codehere 1.0.1 → 1.0.3
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 +35 -193
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -105
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- package/dist/testing/replay-adapter.js +0 -147
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canonical file-collection rules for the trust scanner.
|
|
3
|
-
*
|
|
4
|
-
* Historic bug: IGNORE_DIRS and the test-file filter lived in three places
|
|
5
|
-
* (trust-command, mcp-helpers, report-command) and drifted. The v0.3.0-alpha
|
|
6
|
-
* shipreadiness fix patched two of them; report-command kept the old rules
|
|
7
|
-
* and produced different finding counts than `trust` for the same directory.
|
|
8
|
-
*
|
|
9
|
-
* Single source of truth now — every scanner entry point imports from here.
|
|
10
|
-
*/
|
|
11
|
-
import { readdirSync, statSync } from 'fs';
|
|
12
|
-
import { join, extname } from 'path';
|
|
13
|
-
export const CODE_EXTENSIONS = new Set([
|
|
14
|
-
'.ts',
|
|
15
|
-
'.tsx',
|
|
16
|
-
'.js',
|
|
17
|
-
'.jsx',
|
|
18
|
-
'.mjs',
|
|
19
|
-
'.cjs',
|
|
20
|
-
'.py',
|
|
21
|
-
'.rb',
|
|
22
|
-
'.go',
|
|
23
|
-
'.java',
|
|
24
|
-
'.cs',
|
|
25
|
-
'.php',
|
|
26
|
-
'.sh',
|
|
27
|
-
'.bash',
|
|
28
|
-
'.zsh',
|
|
29
|
-
'.sql',
|
|
30
|
-
'.html',
|
|
31
|
-
'.vue',
|
|
32
|
-
'.svelte',
|
|
33
|
-
]);
|
|
34
|
-
export const IGNORE_DIRS = new Set([
|
|
35
|
-
// JS/TS
|
|
36
|
-
'node_modules',
|
|
37
|
-
'dist',
|
|
38
|
-
'build',
|
|
39
|
-
'.next',
|
|
40
|
-
'.nuxt',
|
|
41
|
-
'.svelte-kit',
|
|
42
|
-
'.turbo',
|
|
43
|
-
'.parcel-cache',
|
|
44
|
-
// Python
|
|
45
|
-
'venv',
|
|
46
|
-
'__pycache__',
|
|
47
|
-
'.pytest_cache',
|
|
48
|
-
'.ruff_cache',
|
|
49
|
-
'.mypy_cache',
|
|
50
|
-
'.tox',
|
|
51
|
-
'__pypackages__',
|
|
52
|
-
// Rust / Go / PHP / Java / .NET
|
|
53
|
-
'target',
|
|
54
|
-
'vendor',
|
|
55
|
-
'.gradle',
|
|
56
|
-
'obj',
|
|
57
|
-
// VCS / tooling / coverage / codehere
|
|
58
|
-
'.git',
|
|
59
|
-
'.hg',
|
|
60
|
-
'.svn',
|
|
61
|
-
'coverage',
|
|
62
|
-
'.cache',
|
|
63
|
-
'.codehere',
|
|
64
|
-
'.codehere-cache',
|
|
65
|
-
// Cloned reference repos used for dogfooding / comparison — not the user's code.
|
|
66
|
-
// Codehere convention; mirrors how `.git` is hidden tooling-data, not source.
|
|
67
|
-
'.oss-repos',
|
|
68
|
-
// Test fixtures / mocks (intentionally-vulnerable code used as scanner inputs)
|
|
69
|
-
'fixtures',
|
|
70
|
-
'test-fixtures',
|
|
71
|
-
'__fixtures__',
|
|
72
|
-
'__mocks__',
|
|
73
|
-
]);
|
|
74
|
-
// Test files contain deliberate vulnerability patterns as scanner inputs —
|
|
75
|
-
// SQL injection strings, hardcoded "credentials" etc. — that inflate the
|
|
76
|
-
// finding count without being real production risks. Skip by default.
|
|
77
|
-
export const TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/;
|
|
78
|
-
export function shouldIgnoreDir(name) {
|
|
79
|
-
if (IGNORE_DIRS.has(name))
|
|
80
|
-
return true;
|
|
81
|
-
// Python virtualenv variants — .venv, .venv311, .venv-py3, venv3.11, env-py3
|
|
82
|
-
if (name.startsWith('.venv') || name.startsWith('venv'))
|
|
83
|
-
return true;
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
export function shouldIgnoreFile(name) {
|
|
87
|
-
return TEST_FILE_RE.test(name);
|
|
88
|
-
}
|
|
89
|
-
export function isCodeFile(name) {
|
|
90
|
-
return CODE_EXTENSIONS.has(extname(name)) && !shouldIgnoreFile(name);
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Walk a directory and return paths of code files worth scanning.
|
|
94
|
-
* If `target` is a single file, it is returned as-is (explicit targets
|
|
95
|
-
* are never excluded — user pointed at it on purpose).
|
|
96
|
-
*/
|
|
97
|
-
export function collectCodeFiles(target) {
|
|
98
|
-
const stat = statSync(target);
|
|
99
|
-
if (stat.isFile())
|
|
100
|
-
return [target];
|
|
101
|
-
const files = [];
|
|
102
|
-
function walk(dir) {
|
|
103
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
104
|
-
if (shouldIgnoreDir(entry.name))
|
|
105
|
-
continue;
|
|
106
|
-
const full = join(dir, entry.name);
|
|
107
|
-
if (entry.isDirectory()) {
|
|
108
|
-
walk(full);
|
|
109
|
-
}
|
|
110
|
-
else if (isCodeFile(entry.name)) {
|
|
111
|
-
files.push(full);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
walk(target);
|
|
116
|
-
return files;
|
|
117
|
-
}
|
|
118
|
-
//# sourceMappingURL=file-collection.js.map
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI fix + prove-closed for a CODE danger (not a leaked secret) — browser-auth, injection, unsafe HTML.
|
|
3
|
-
* Spawns claude-code (subscription, async so it never blocks the daemon) to fix the specific issue,
|
|
4
|
-
* then RE-SCANS to prove that exact danger no longer fires. Honest if it can't. Uses model spend.
|
|
5
|
-
* The verify-loop (validated 0% false-positive) applied to the scan. docs/EXECUTION_PLAN step 2.
|
|
6
|
-
*/
|
|
7
|
-
import { spawn } from 'node:child_process';
|
|
8
|
-
import { shipScan } from './ship-scan.js';
|
|
9
|
-
// Same pre-granted edit tools the daemon's claude-code adapter uses for headless -p mode.
|
|
10
|
-
const CLAUDE_FLAGS = [
|
|
11
|
-
'--output-format',
|
|
12
|
-
'stream-json',
|
|
13
|
-
'--verbose',
|
|
14
|
-
'--allowedTools',
|
|
15
|
-
'Edit',
|
|
16
|
-
'Write',
|
|
17
|
-
'Read',
|
|
18
|
-
'Grep',
|
|
19
|
-
'Glob',
|
|
20
|
-
'MultiEdit',
|
|
21
|
-
];
|
|
22
|
-
const baseName = (p) => {
|
|
23
|
-
const s = String(p || '');
|
|
24
|
-
return s.split('/').pop() || s;
|
|
25
|
-
};
|
|
26
|
-
/** Turn one claude stream-json event into a plain-language step ('' = nothing worth showing). */
|
|
27
|
-
function stepLabel(ev) {
|
|
28
|
-
if (!ev || ev.type !== 'assistant')
|
|
29
|
-
return '';
|
|
30
|
-
const content = ev.message?.content;
|
|
31
|
-
if (!Array.isArray(content))
|
|
32
|
-
return '';
|
|
33
|
-
for (const c of content) {
|
|
34
|
-
if (c?.type !== 'tool_use')
|
|
35
|
-
continue;
|
|
36
|
-
const name = String(c.name || '');
|
|
37
|
-
const input = c.input || {};
|
|
38
|
-
const f = baseName(input.file_path || input.path);
|
|
39
|
-
if (name === 'Edit' || name === 'MultiEdit' || name === 'Write')
|
|
40
|
-
return `Editing ${f}`;
|
|
41
|
-
if (name === 'Read')
|
|
42
|
-
return `Reading ${f}`;
|
|
43
|
-
if (name === 'Grep' || name === 'Glob')
|
|
44
|
-
return 'Searching the code';
|
|
45
|
-
return `Working (${name})`;
|
|
46
|
-
}
|
|
47
|
-
return '';
|
|
48
|
-
}
|
|
49
|
-
export async function fixCodeVuln(dir, top, onStep) {
|
|
50
|
-
const prompt = `There is a real security vulnerability in ${top.file} (around line ${top.line}): ${top.what} ` +
|
|
51
|
-
`Fix it properly so the app still works — change the code, don't just comment it. Edit only the file(s) needed in this project.`;
|
|
52
|
-
const env = { ...process.env };
|
|
53
|
-
delete env.ANTHROPIC_API_KEY; // use the Claude Code subscription, not pay-as-you-go
|
|
54
|
-
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
55
|
-
// Don't repeat the same step twice in a row — keeps the live feed readable.
|
|
56
|
-
let lastLabel = '';
|
|
57
|
-
const step = (label) => {
|
|
58
|
-
if (!label || label === lastLabel)
|
|
59
|
-
return;
|
|
60
|
-
lastLabel = label;
|
|
61
|
-
try {
|
|
62
|
-
onStep?.(label);
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
/* a dead client must never crash the fix */
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
step('Starting the AI fix');
|
|
69
|
-
await new Promise((resolve) => {
|
|
70
|
-
let child;
|
|
71
|
-
try {
|
|
72
|
-
// Capture stdout (the stream-json feed) so we can surface live steps; stderr stays quiet.
|
|
73
|
-
child = spawn('claude', ['-p', prompt, ...CLAUDE_FLAGS], {
|
|
74
|
-
cwd: dir,
|
|
75
|
-
env: env,
|
|
76
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
resolve();
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
let buf = '';
|
|
84
|
-
child.stdout?.on('data', (chunk) => {
|
|
85
|
-
buf += chunk.toString();
|
|
86
|
-
let nl;
|
|
87
|
-
while ((nl = buf.indexOf('\n')) >= 0) {
|
|
88
|
-
const line = buf.slice(0, nl).trim();
|
|
89
|
-
buf = buf.slice(nl + 1);
|
|
90
|
-
if (!line)
|
|
91
|
-
continue;
|
|
92
|
-
let ev;
|
|
93
|
-
try {
|
|
94
|
-
ev = JSON.parse(line);
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
step(stepLabel(ev));
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
const timer = setTimeout(() => {
|
|
103
|
-
try {
|
|
104
|
-
child.kill('SIGKILL');
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
/* best effort */
|
|
108
|
-
}
|
|
109
|
-
resolve();
|
|
110
|
-
}, 180_000);
|
|
111
|
-
child.on('close', () => {
|
|
112
|
-
clearTimeout(timer);
|
|
113
|
-
resolve();
|
|
114
|
-
});
|
|
115
|
-
child.on('error', () => {
|
|
116
|
-
clearTimeout(timer);
|
|
117
|
-
resolve();
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
step('Re-scanning to prove the danger is gone');
|
|
121
|
-
// PROVE: re-scan and check the SAME danger (file + ruleId) is gone.
|
|
122
|
-
const after = shipScan(dir);
|
|
123
|
-
const stillThere = after.findings.some((f) => f.file === top.file && f.ruleId === top.ruleId);
|
|
124
|
-
// A finding can only be proven closed by the scanner that FOUND it. If that scanner failed on the
|
|
125
|
-
// re-scan, the danger "disappears" for the same reason a switched-off smoke alarm reports no fire.
|
|
126
|
-
// Absence of a finding from a scanner that never ran is not proof, and this loop's whole value is
|
|
127
|
-
// that its proof means something. (codehere's own vibeChecks run in-process, so they always ran.)
|
|
128
|
-
const proverRan = top.tool === 'codehere' || after.ran[top.tool];
|
|
129
|
-
if (!stillThere && !proverRan) {
|
|
130
|
-
return {
|
|
131
|
-
ok: true,
|
|
132
|
-
provenClosed: false,
|
|
133
|
-
message: `The AI rewrote ${top.file}, but codehere could not prove the fix: ${top.tool} failed to complete the re-scan, so the "${top.ruleId}" danger is unverified rather than gone. Re-run the scan once ${top.tool} is working.`,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
ok: true,
|
|
138
|
-
provenClosed: !stillThere,
|
|
139
|
-
message: !stillThere
|
|
140
|
-
? `Proven closed: the AI rewrote ${top.file} and codehere re-scanned it — the "${top.ruleId}" danger is gone. Give the change a quick look before you ship.`
|
|
141
|
-
: `The AI tried, but codehere re-scanned and the "${top.ruleId}" danger is STILL in ${top.file}. Not fixed — this one needs your eyes (or a different fix).`,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
//# sourceMappingURL=fix-code.js.map
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic "fix + prove closed" for an exposed secret — the #1 vibe-coder ship-blocker.
|
|
3
|
-
* ZERO model tokens. Moves the hardcoded secret out of your shippable code (→ process.env + a
|
|
4
|
-
* gitignored .env), then PROVES it by re-reading the source. Honest: you must still ROTATE the key
|
|
5
|
-
* (moving it doesn't undo the exposure that already happened). See docs/PRODUCT_10X.md (R3).
|
|
6
|
-
*/
|
|
7
|
-
import { spawnSync } from 'node:child_process';
|
|
8
|
-
import { mkdtempSync, readFileSync, writeFileSync, existsSync, appendFileSync } from 'node:fs';
|
|
9
|
-
import { tmpdir } from 'node:os';
|
|
10
|
-
import { join, relative, isAbsolute } from 'node:path';
|
|
11
|
-
function topSecret(dir) {
|
|
12
|
-
const tmp = join(mkdtempSync(join(tmpdir(), 'fixsec-')), 'gl.json');
|
|
13
|
-
spawnSync('gitleaks', ['detect', '--no-git', '--source', dir, '-f', 'json', '-r', tmp], { stdio: 'ignore' });
|
|
14
|
-
if (!existsSync(tmp))
|
|
15
|
-
return null;
|
|
16
|
-
let arr;
|
|
17
|
-
try {
|
|
18
|
-
arr = JSON.parse(readFileSync(tmp, 'utf8'));
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
if (!arr || !arr.length)
|
|
24
|
-
return null;
|
|
25
|
-
// Prefer a secret still living in shippable source (not one already moved into a .env).
|
|
26
|
-
const f = arr.find((x) => !String(x.File || '').endsWith('.env')) || arr[0];
|
|
27
|
-
return {
|
|
28
|
-
file: String(f.File),
|
|
29
|
-
secret: String(f.Secret),
|
|
30
|
-
line: Number(f.StartLine || 0),
|
|
31
|
-
ruleId: String(f.RuleID || 'secret'),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
function deriveVarName(line, ruleId) {
|
|
35
|
-
const m = line.match(/(?:const|let|var)?\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*[:=]/);
|
|
36
|
-
let name = m ? m[1] : `SECRET_${ruleId}`;
|
|
37
|
-
name = name
|
|
38
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
39
|
-
.toUpperCase()
|
|
40
|
-
.replace(/[^A-Z0-9_]/g, '_');
|
|
41
|
-
return name || 'APP_SECRET';
|
|
42
|
-
}
|
|
43
|
-
export function fixExposedSecret(dir) {
|
|
44
|
-
const t = topSecret(dir);
|
|
45
|
-
if (!t)
|
|
46
|
-
return {
|
|
47
|
-
ok: false,
|
|
48
|
-
reason: 'no_secret',
|
|
49
|
-
message: 'The top issue here is a code change, not a leaked key — auto-fix for code vulnerabilities is the next step (it needs an agent).',
|
|
50
|
-
};
|
|
51
|
-
if (!existsSync(t.file))
|
|
52
|
-
return { ok: false, reason: 'file_gone', message: 'The flagged file is no longer there.' };
|
|
53
|
-
let src = readFileSync(t.file, 'utf8');
|
|
54
|
-
if (!src.includes(t.secret))
|
|
55
|
-
return { ok: false, reason: 'not_found', message: 'Could not locate the secret in the file to replace.' };
|
|
56
|
-
const lineText = src.split('\n').find((l) => l.includes(t.secret)) || '';
|
|
57
|
-
const varName = deriveVarName(lineText, t.ruleId);
|
|
58
|
-
// Replace the literal with process.env.VARNAME (quoted forms first, bare as a fallback).
|
|
59
|
-
src = src
|
|
60
|
-
.split(`"${t.secret}"`)
|
|
61
|
-
.join(`process.env.${varName}`)
|
|
62
|
-
.split(`'${t.secret}'`)
|
|
63
|
-
.join(`process.env.${varName}`)
|
|
64
|
-
.split(t.secret)
|
|
65
|
-
.join(`process.env.${varName}`);
|
|
66
|
-
writeFileSync(t.file, src);
|
|
67
|
-
// Move the value to a gitignored .env.
|
|
68
|
-
const env = join(dir, '.env');
|
|
69
|
-
if (!existsSync(env) || !readFileSync(env, 'utf8').includes(`${varName}=`))
|
|
70
|
-
appendFileSync(env, `${varName}=${t.secret}\n`);
|
|
71
|
-
const gi = join(dir, '.gitignore');
|
|
72
|
-
const giTxt = existsSync(gi) ? readFileSync(gi, 'utf8') : '';
|
|
73
|
-
if (!/^\.env\s*$/m.test(giTxt))
|
|
74
|
-
writeFileSync(gi, (giTxt && !giTxt.endsWith('\n') ? `${giTxt}\n` : giTxt) + '.env\n');
|
|
75
|
-
// PROVE CLOSED: the secret must be gone from shippable source.
|
|
76
|
-
const provenClosed = !readFileSync(t.file, 'utf8').includes(t.secret);
|
|
77
|
-
const file = isAbsolute(t.file) ? relative(dir, t.file) : t.file;
|
|
78
|
-
return {
|
|
79
|
-
ok: true,
|
|
80
|
-
file,
|
|
81
|
-
varName,
|
|
82
|
-
provenClosed,
|
|
83
|
-
rotate: true,
|
|
84
|
-
message: provenClosed
|
|
85
|
-
? `Proven closed: the secret is gone from ${file} — it now reads process.env.${varName}, and the value moved to .env (now gitignored, so it won't ship or get committed). ⚠ You MUST still rotate this key now — it was already exposed, so revoke the old value.`
|
|
86
|
-
: `Moved the secret to .env, but couldn't fully verify removal from ${file} — please check it by hand.`,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
//# sourceMappingURL=fix-secret.js.map
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { writeFileAtomic } from '../storage/atomic-write.js';
|
|
2
|
-
/**
|
|
3
|
-
* Per-cwd permissions config — P18 slice 1 primitive.
|
|
4
|
-
*
|
|
5
|
-
* Reads `.codehere/permissions.yaml` from a project's cwd and lets the
|
|
6
|
-
* trust-scorer SKIP findings whose matched text fits an allow pattern.
|
|
7
|
-
*
|
|
8
|
-
* Use case: codehere blocks `rm -rf node_modules` because the pattern
|
|
9
|
-
* matches `rm -rf /`. The user knows it's safe in THIS cwd. They run
|
|
10
|
-
* `codehere permission allow "rm -rf node_modules"` (slice 2 CLI) and
|
|
11
|
-
* future blocks for that exact pattern in this cwd are skipped.
|
|
12
|
-
*
|
|
13
|
-
* Schema (.codehere/permissions.yaml):
|
|
14
|
-
*
|
|
15
|
-
* allow:
|
|
16
|
-
* - pattern: "rm -rf node_modules"
|
|
17
|
-
* reason: "OK to nuke deps in this repo"
|
|
18
|
-
* addedAt: 2026-05-08T20:00:00Z
|
|
19
|
-
* - pattern: "ANTHROPIC_API_KEY"
|
|
20
|
-
* reason: "Doc explains env vars; not actually leaking"
|
|
21
|
-
*
|
|
22
|
-
* Allow patterns are STRINGS not regex — exact substring match against
|
|
23
|
-
* the audit-finding `matched` field. Keeps the schema simple +
|
|
24
|
-
* non-technical-user friendly. Regex support is a v0.7 follow-up.
|
|
25
|
-
*
|
|
26
|
-
* Per claim_user_persona HIGH: the YAML is human-readable; non-
|
|
27
|
-
* technical users can edit it directly. Per overconfidence-guard:
|
|
28
|
-
* we DON'T claim the allow-list is "secure" — it's user CONSENT,
|
|
29
|
-
* not codehere certification.
|
|
30
|
-
*/
|
|
31
|
-
import { existsSync, readFileSync } from 'fs';
|
|
32
|
-
import { join } from 'path';
|
|
33
|
-
const EMPTY = { allow: [], deny: [] };
|
|
34
|
-
/**
|
|
35
|
-
* Load .codehere/permissions.yaml from a cwd. Returns empty config if
|
|
36
|
-
* the file doesn't exist or fails to parse. Defensive: never throws —
|
|
37
|
-
* caller treats absence as "no overrides", which is the safest default
|
|
38
|
-
* (the scanner blocks normally).
|
|
39
|
-
*/
|
|
40
|
-
export function loadPermissions(cwd) {
|
|
41
|
-
if (!cwd || typeof cwd !== 'string')
|
|
42
|
-
return EMPTY;
|
|
43
|
-
const file = join(cwd, '.codehere', 'permissions.yaml');
|
|
44
|
-
if (!existsSync(file))
|
|
45
|
-
return EMPTY;
|
|
46
|
-
let raw;
|
|
47
|
-
try {
|
|
48
|
-
raw = readFileSync(file, 'utf-8');
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return EMPTY;
|
|
52
|
-
}
|
|
53
|
-
return parsePermissionsYaml(raw);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Tiny YAML subset parser — only handles the shape this file uses
|
|
57
|
-
* (top-level allow/deny lists of {pattern, reason, addedAt} objects).
|
|
58
|
-
* No external dep; the codehere repo already avoids the `yaml` package
|
|
59
|
-
* for read paths to keep cold-start fast. Format errors yield empty
|
|
60
|
-
* config (same posture as missing file).
|
|
61
|
-
*/
|
|
62
|
-
export function parsePermissionsYaml(raw) {
|
|
63
|
-
const cfg = { allow: [], deny: [] };
|
|
64
|
-
let bucket = null;
|
|
65
|
-
let pending = null;
|
|
66
|
-
for (const line of raw.split('\n')) {
|
|
67
|
-
const stripped = line.replace(/#.*$/, '').trimEnd();
|
|
68
|
-
if (!stripped.trim())
|
|
69
|
-
continue;
|
|
70
|
-
// Top-level key: "allow:" or "deny:"
|
|
71
|
-
const top = stripped.match(/^(allow|deny)\s*:\s*$/);
|
|
72
|
-
if (top) {
|
|
73
|
-
// Flush any pending entry into the previous bucket
|
|
74
|
-
if (pending && bucket && pending.pattern)
|
|
75
|
-
cfg[bucket].push(pending);
|
|
76
|
-
pending = null;
|
|
77
|
-
bucket = top[1];
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
if (!bucket)
|
|
81
|
-
continue;
|
|
82
|
-
// List entry start: " - pattern: ..."
|
|
83
|
-
const listStart = stripped.match(/^\s*-\s+pattern\s*:\s*(.+)$/);
|
|
84
|
-
if (listStart) {
|
|
85
|
-
if (pending && pending.pattern)
|
|
86
|
-
cfg[bucket].push(pending);
|
|
87
|
-
pending = { pattern: unquote(listStart[1]) };
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
// Continuation field on a pending entry
|
|
91
|
-
const cont = stripped.match(/^\s+(reason|addedAt)\s*:\s*(.+)$/);
|
|
92
|
-
if (cont && pending) {
|
|
93
|
-
const key = cont[1];
|
|
94
|
-
pending[key] = unquote(cont[2]);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (pending && bucket && pending.pattern)
|
|
98
|
-
cfg[bucket].push(pending);
|
|
99
|
-
return cfg;
|
|
100
|
-
}
|
|
101
|
-
function unquote(s) {
|
|
102
|
-
const t = s.trim();
|
|
103
|
-
if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) {
|
|
104
|
-
return t.slice(1, -1);
|
|
105
|
-
}
|
|
106
|
-
return t;
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Check whether a finding's matched text is allow-listed in this cwd.
|
|
110
|
-
* Substring match — exact-text patterns are simpler for non-technical
|
|
111
|
-
* users than regex. Returns the matching entry (for audit-trail
|
|
112
|
-
* provenance) or null if no match.
|
|
113
|
-
*/
|
|
114
|
-
export function isAllowed(matchedText, cwd) {
|
|
115
|
-
const cfg = loadPermissions(cwd);
|
|
116
|
-
for (const entry of cfg.allow) {
|
|
117
|
-
if (entry.pattern && matchedText.includes(entry.pattern))
|
|
118
|
-
return entry;
|
|
119
|
-
}
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Filter a findings list against the allow-list for a cwd. Returns
|
|
124
|
-
* the findings that survived (i.e., were NOT allow-listed). Used by
|
|
125
|
-
* the trust-scorer / pre-prompt-scan to drop findings that the user
|
|
126
|
-
* has explicitly approved.
|
|
127
|
-
*
|
|
128
|
-
* Side-effect-free: the allow-listed findings are dropped from the
|
|
129
|
-
* caller's perspective. The audit log writer can still record the
|
|
130
|
-
* full finding list IF the caller wants (then mark which were
|
|
131
|
-
* allow-listed). This function is the simple "what should we still
|
|
132
|
-
* block on" filter.
|
|
133
|
-
*/
|
|
134
|
-
export function filterAllowed(findings, cwd) {
|
|
135
|
-
if (findings.length === 0)
|
|
136
|
-
return { surviving: [], allowed: [] };
|
|
137
|
-
const cfg = loadPermissions(cwd);
|
|
138
|
-
if (cfg.allow.length === 0)
|
|
139
|
-
return { surviving: findings, allowed: [] };
|
|
140
|
-
const surviving = [];
|
|
141
|
-
const allowed = [];
|
|
142
|
-
for (const f of findings) {
|
|
143
|
-
const hit = cfg.allow.find((e) => e.pattern && f.matched.includes(e.pattern));
|
|
144
|
-
if (hit)
|
|
145
|
-
allowed.push({ finding: f, entry: hit });
|
|
146
|
-
else
|
|
147
|
-
surviving.push(f);
|
|
148
|
-
}
|
|
149
|
-
return { surviving, allowed };
|
|
150
|
-
}
|
|
151
|
-
// ── Write surface (P18 slice 3) ─────────────────────────────────────────────
|
|
152
|
-
// Shared by the CLI (`codehere permission allow ...`) and the HTTP write
|
|
153
|
-
// endpoint (POST /api/permissions). Single source of truth for the
|
|
154
|
-
// idempotent-add semantics + the YAML serializer.
|
|
155
|
-
import { mkdirSync } from 'fs';
|
|
156
|
-
/**
|
|
157
|
-
* Idempotent add — if the pattern already exists in the bucket, returns
|
|
158
|
-
* status='already_present' with the existing entry. Otherwise appends +
|
|
159
|
-
* writes the file. Writes via Write tool semantics (overwrites the
|
|
160
|
-
* whole YAML each time; entries are O(N) so re-serialization is cheap).
|
|
161
|
-
*/
|
|
162
|
-
export function addPermissionEntry(input) {
|
|
163
|
-
const bucket = input.bucket ?? 'allow';
|
|
164
|
-
const cfg = loadPermissions(input.cwd);
|
|
165
|
-
const list = cfg[bucket];
|
|
166
|
-
const existing = list.find((e) => e.pattern === input.pattern);
|
|
167
|
-
if (existing) {
|
|
168
|
-
return {
|
|
169
|
-
status: 'already_present',
|
|
170
|
-
bucket,
|
|
171
|
-
entry: existing,
|
|
172
|
-
filePath: join(input.cwd, '.codehere', 'permissions.yaml'),
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
const entry = {
|
|
176
|
-
pattern: input.pattern,
|
|
177
|
-
...(input.reason ? { reason: input.reason } : {}),
|
|
178
|
-
addedAt: new Date().toISOString(),
|
|
179
|
-
};
|
|
180
|
-
cfg[bucket].push(entry);
|
|
181
|
-
const filePath = writePermissionsConfig(input.cwd, cfg);
|
|
182
|
-
return { status: 'added', bucket, entry, filePath };
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Idempotent remove — drops every entry whose pattern matches across
|
|
186
|
-
* BOTH buckets. Returns count of removed entries.
|
|
187
|
-
*/
|
|
188
|
-
export function removePermissionEntry(cwd, pattern) {
|
|
189
|
-
const cfg = loadPermissions(cwd);
|
|
190
|
-
const before = cfg.allow.length + cfg.deny.length;
|
|
191
|
-
cfg.allow = cfg.allow.filter((e) => e.pattern !== pattern);
|
|
192
|
-
cfg.deny = cfg.deny.filter((e) => e.pattern !== pattern);
|
|
193
|
-
const after = cfg.allow.length + cfg.deny.length;
|
|
194
|
-
const removed = before - after;
|
|
195
|
-
if (removed > 0)
|
|
196
|
-
writePermissionsConfig(cwd, cfg);
|
|
197
|
-
return { removed, filePath: join(cwd, '.codehere', 'permissions.yaml') };
|
|
198
|
-
}
|
|
199
|
-
function writePermissionsConfig(cwd, cfg) {
|
|
200
|
-
const dir = join(cwd, '.codehere');
|
|
201
|
-
if (!existsSync(dir))
|
|
202
|
-
mkdirSync(dir, { recursive: true });
|
|
203
|
-
const filePath = join(dir, 'permissions.yaml');
|
|
204
|
-
writeFileAtomic(filePath, serializePermissionsYaml(cfg));
|
|
205
|
-
return filePath;
|
|
206
|
-
}
|
|
207
|
-
export function serializePermissionsYaml(cfg) {
|
|
208
|
-
const out = [];
|
|
209
|
-
out.push('# codehere permissions — per-folder allow/deny list');
|
|
210
|
-
out.push('# See: codehere permission --help');
|
|
211
|
-
out.push('');
|
|
212
|
-
for (const bucket of ['allow', 'deny']) {
|
|
213
|
-
out.push(`${bucket}:`);
|
|
214
|
-
if (cfg[bucket].length === 0) {
|
|
215
|
-
out.push(' []');
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
for (const e of cfg[bucket]) {
|
|
219
|
-
out.push(` - pattern: ${quoteYaml(e.pattern)}`);
|
|
220
|
-
if (e.reason)
|
|
221
|
-
out.push(` reason: ${quoteYaml(e.reason)}`);
|
|
222
|
-
if (e.addedAt)
|
|
223
|
-
out.push(` addedAt: ${quoteYaml(e.addedAt)}`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
out.push('');
|
|
227
|
-
}
|
|
228
|
-
return out.join('\n');
|
|
229
|
-
}
|
|
230
|
-
function quoteYaml(s) {
|
|
231
|
-
if (s.includes('"') && !s.includes("'"))
|
|
232
|
-
return `'${s}'`;
|
|
233
|
-
return `"${s.replace(/"/g, '\\"')}"`;
|
|
234
|
-
}
|
|
235
|
-
//# sourceMappingURL=permissions-config.js.map
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The shape a scan finding takes, shared by everything that produces or consumes one.
|
|
3
|
-
*
|
|
4
|
-
* These lived in ship-scan.ts, which made vibe-checks.ts import from the module that imports
|
|
5
|
-
* it — a type-only cycle, erased at compile time and harmless at runtime, but the release
|
|
6
|
-
* checklist requires zero circular dependencies and a type-only cycle is still a cycle a
|
|
7
|
-
* later value import can turn into a real one.
|
|
8
|
-
*/
|
|
9
|
-
export {};
|
|
10
|
-
//# sourceMappingURL=scan-types.js.map
|