@cybedefend/vibedefend 1.4.1 → 1.4.2
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 +10 -0
- package/dist/custom-regions.js +68 -0
- package/dist/custom-regions.js.map +1 -1
- package/dist/guards-evaluator/preprocess.js +114 -3
- package/dist/guards-evaluator/preprocess.js.map +1 -1
- package/dist/hook-runner.js +4962 -373
- package/dist/hooks/runtime/guard-rules-cache.js +109 -28
- package/dist/hooks/runtime/guard-rules-cache.js.map +1 -1
- package/dist/hooks/runtime/resolve.js +20 -2
- package/dist/hooks/runtime/resolve.js.map +1 -1
- package/dist/hooks/runtime/rule-schema.js +113 -0
- package/dist/hooks/runtime/rule-schema.js.map +1 -0
- package/dist/hooks/runtime/self-update-check.js +36 -2
- package/dist/hooks/runtime/self-update-check.js.map +1 -1
- package/dist/hooks/runtime/trusted-api-base.js +194 -0
- package/dist/hooks/runtime/trusted-api-base.js.map +1 -0
- package/dist/link.js +29 -3
- package/dist/link.js.map +1 -1
- package/package.json +16 -9
package/README.md
CHANGED
|
@@ -111,6 +111,16 @@ npx -y @cybedefend/vibedefend@latest link --project <PROJECT_ID>
|
|
|
111
111
|
That file is now **optional** for GitHub/GitLab-linked projects, but it
|
|
112
112
|
remains a supported override everywhere — when present, it always wins over
|
|
113
113
|
git-origin auto-resolution (as does the `CYBEDEFEND_PROJECT_ID` env var).
|
|
114
|
+
|
|
115
|
+
`.cybedefend/config.json` also accepts an optional `apiBase`. Because the file
|
|
116
|
+
is committed — and therefore comes from whoever published the repository you
|
|
117
|
+
just cloned — that key is only honoured when it points at an origin **you**
|
|
118
|
+
already trust: a built-in region, the region you installed against
|
|
119
|
+
(`~/.cybedefend/runtime-config.json`), or a custom region declared in your own
|
|
120
|
+
`~/.cybedefend/regions.json`. Anything else is ignored with a warning and the
|
|
121
|
+
hooks keep talking to your configured region. To point the hooks at a cluster
|
|
122
|
+
of your own, add it to `~/.cybedefend/regions.json` or re-run
|
|
123
|
+
`vibedefend install` — or set `CYBEDEFEND_API_BASE` in your own environment.
|
|
114
124
|
You can grab the UUID from the project page on the EU dashboard
|
|
115
125
|
([eu.cybedefend.com](https://eu.cybedefend.com)) or the US dashboard
|
|
116
126
|
([us.cybedefend.com](https://us.cybedefend.com)).
|
package/dist/custom-regions.js
CHANGED
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
* `dashboardUrl` is optional (defaults to derivation from `apiBase` —
|
|
19
19
|
* see `deriveDashboardUrl` in `src/endpoints.ts`).
|
|
20
20
|
*
|
|
21
|
+
* `mcpUrl`, `apiBase` and `dashboardUrl` must be https URLs with a plain host
|
|
22
|
+
* and no embedded credentials — except on loopback, where plain http is
|
|
23
|
+
* allowed because the localhost stack is exactly what this file is for. Being
|
|
24
|
+
* a well-formed non-empty string is not enough: candidate #2 in the lookup
|
|
25
|
+
* order is `process.cwd()`, so a cloned repository can ship this file and add
|
|
26
|
+
* its own entry — labelled with its own text — to the install picker.
|
|
27
|
+
*
|
|
21
28
|
* The loader never throws — bad JSON or malformed entries log a warning
|
|
22
29
|
* and degrade to "no custom regions" so a broken file can't break the
|
|
23
30
|
* install picker.
|
|
@@ -33,6 +40,54 @@ const REQUIRED_STRING_FIELDS = [
|
|
|
33
40
|
'apiBase',
|
|
34
41
|
'mcpName',
|
|
35
42
|
];
|
|
43
|
+
/** Fields that must look like a real endpoint, not merely be a non-empty string. */
|
|
44
|
+
const URL_FIELDS = [
|
|
45
|
+
'mcpUrl',
|
|
46
|
+
'apiBase',
|
|
47
|
+
'dashboardUrl',
|
|
48
|
+
];
|
|
49
|
+
/**
|
|
50
|
+
* Loopback hosts, the one place plain http is legitimate — the localhost
|
|
51
|
+
* docker-compose stack this file exists to support. Anything else must be
|
|
52
|
+
* https: `./vibedefend.regions.json` is read from `process.cwd()`, so a cloned
|
|
53
|
+
* repository can ship one, and every entry ends up in the `vibedefend install`
|
|
54
|
+
* picker pointing `apiBase`/`mcpUrl` wherever it likes.
|
|
55
|
+
*/
|
|
56
|
+
function isLoopbackHost(hostname) {
|
|
57
|
+
const host = hostname.toLowerCase();
|
|
58
|
+
return (host === 'localhost' ||
|
|
59
|
+
host.endsWith('.localhost') ||
|
|
60
|
+
host === '127.0.0.1' ||
|
|
61
|
+
host === '::1' ||
|
|
62
|
+
host === '[::1]');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Semantic check on an endpoint field. Returns an error string, or `null` when
|
|
66
|
+
* the value is acceptable. Structural validation (non-empty string) happens
|
|
67
|
+
* separately and runs first.
|
|
68
|
+
*/
|
|
69
|
+
function urlProblem(field, value) {
|
|
70
|
+
let url;
|
|
71
|
+
try {
|
|
72
|
+
url = new URL(value);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return `${field} is not a valid URL`;
|
|
76
|
+
}
|
|
77
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
|
78
|
+
return `${field} must use https (got "${url.protocol}")`;
|
|
79
|
+
}
|
|
80
|
+
if (url.username.length > 0 || url.password.length > 0) {
|
|
81
|
+
return `${field} must not embed credentials`;
|
|
82
|
+
}
|
|
83
|
+
if (url.hostname.length === 0) {
|
|
84
|
+
return `${field} has no host`;
|
|
85
|
+
}
|
|
86
|
+
if (url.protocol === 'http:' && !isLoopbackHost(url.hostname)) {
|
|
87
|
+
return `${field} must use https (plain http is only allowed for localhost)`;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
36
91
|
/**
|
|
37
92
|
* Resolve the candidate paths in lookup order. Filters out empty/undefined
|
|
38
93
|
* env-var values so we don't accidentally try to read `process.cwd()` etc.
|
|
@@ -84,6 +139,19 @@ function parseRegionsFile(path) {
|
|
|
84
139
|
log.warn(`Custom regions file ${path}: entry #${i} missing or empty required field(s) [${missing.join(', ')}] — skipped.`);
|
|
85
140
|
return;
|
|
86
141
|
}
|
|
142
|
+
// Structurally fine — now check the endpoints actually point somewhere
|
|
143
|
+
// sane. This file can come from `process.cwd()`, i.e. from a repository.
|
|
144
|
+
const urlErrors = URL_FIELDS.flatMap((field) => {
|
|
145
|
+
const value = obj[field];
|
|
146
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
147
|
+
return [];
|
|
148
|
+
const problem = urlProblem(String(field), value);
|
|
149
|
+
return problem ? [problem] : [];
|
|
150
|
+
});
|
|
151
|
+
if (urlErrors.length > 0) {
|
|
152
|
+
log.warn(`Custom regions file ${path}: entry #${i} — ${urlErrors.join('; ')} — skipped.`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
87
155
|
valid.push({
|
|
88
156
|
id: obj.id,
|
|
89
157
|
label: obj.label,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-regions.js","sourceRoot":"","sources":["../src/custom-regions.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"custom-regions.js","sourceRoot":"","sources":["../src/custom-regions.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,10 +22,23 @@
|
|
|
22
22
|
* - Only the subcommands that genuinely accept message text. `git push
|
|
23
23
|
* -m` (not a real flag) keeps its argument so we don't lose detection
|
|
24
24
|
* on hypothetical future variants.
|
|
25
|
-
* -
|
|
25
|
+
* - A heredoc is stripped ONLY when it feeds a data-bearing context
|
|
26
|
+
* (`git commit -F - <<EOF`). `bash <<EOF` / `python3 <<EOF` keep their
|
|
27
|
+
* body: that body is CODE, and masking it would be an exploitable
|
|
28
|
+
* blind spot rather than a false-positive fix.
|
|
29
|
+
* - Stripping resets at every shell operator (`;`, `&&`, `||`, `|`),
|
|
30
|
+
* whether the operator arrives as a shell-quote `{op}` object (string
|
|
31
|
+
* path) or as a bare token (argv path — what production uses).
|
|
26
32
|
* So `git commit -m "msg"; cat .env` strips the message but keeps
|
|
27
33
|
* `cat .env` for evaluation.
|
|
28
34
|
*
|
|
35
|
+
* BACK-150: the argv-path operator handling and the heredoc rule above were
|
|
36
|
+
* both missing, and this module had NO tests. `git add -A && git commit -F -
|
|
37
|
+
* <<EOF …` therefore reached the rule regex with its message body intact and
|
|
38
|
+
* blocked a legitimate commit. See tests/preprocess.test.ts — those tests drive
|
|
39
|
+
* the real tokenizer and `evaluate()` rather than hand-built token arrays,
|
|
40
|
+
* because an assumed input shape is what hid this for so long.
|
|
41
|
+
*
|
|
29
42
|
* No I/O, no system access — pure string in, pure string out. Parse failures
|
|
30
43
|
* (unclosed quotes, invalid escapes) fall back to the original input so
|
|
31
44
|
* the scanner keeps running.
|
|
@@ -56,6 +69,41 @@ const DATA_FLAGS_VALUE = new Set([
|
|
|
56
69
|
]);
|
|
57
70
|
/** Flags in `--flag=value` form whose value is data. */
|
|
58
71
|
const DATA_FLAGS_EQUAL = ['--message=', '--file=', '--body=', '--body-file='];
|
|
72
|
+
/**
|
|
73
|
+
* Shell operators that end a command segment.
|
|
74
|
+
*
|
|
75
|
+
* The argv path receives these as BARE STRINGS (the caller's tokenizer splits
|
|
76
|
+
* on spaces and knows nothing about shell grammar), so the `{ op }` object
|
|
77
|
+
* branch below never sees them. Without this set, `git add -A && git commit -m
|
|
78
|
+
* "..."` kept `add` as the segment subcommand and the commit message was never
|
|
79
|
+
* recognised as data — a false positive on every rule whose pattern happened to
|
|
80
|
+
* appear in the message text.
|
|
81
|
+
*/
|
|
82
|
+
const SHELL_OPS = new Set([';', '&&', '||', '|', '|&', '&', '>', '>>', '<', '(', ')']);
|
|
83
|
+
/**
|
|
84
|
+
* Match the opening token of a heredoc redirect: `<<EOF`, `<<-EOF`, `<<'EOF'`.
|
|
85
|
+
* Group 2 is the delimiter word.
|
|
86
|
+
*/
|
|
87
|
+
const HEREDOC_OPEN = /^<<-?\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\1/;
|
|
88
|
+
/**
|
|
89
|
+
* A value the shell EXECUTES rather than passes through: command substitution
|
|
90
|
+
* (`$(…)`, backquotes) or process substitution (`<(…)`, `>(…)`).
|
|
91
|
+
*
|
|
92
|
+
* Masking exists to hide user *text* from the rule regex. A substitution is not
|
|
93
|
+
* text — it runs. Replacing it with `<DATA>` would delete the dangerous part of
|
|
94
|
+
* the command from what the scanner sees while the shell still executes it,
|
|
95
|
+
* turning a false-positive fix into a bypass. Measured before this guard:
|
|
96
|
+
* `git commit -m $(curl … | bash)` reduced to `git commit -m <DATA> … | bash)`,
|
|
97
|
+
* which no longer matched the pipe-to-shell rule.
|
|
98
|
+
*
|
|
99
|
+
* So: never mask these. Let the regex see them.
|
|
100
|
+
*/
|
|
101
|
+
function isExecutableValue(tok) {
|
|
102
|
+
return (tok.startsWith('$(') ||
|
|
103
|
+
tok.startsWith('<(') ||
|
|
104
|
+
tok.startsWith('>(') ||
|
|
105
|
+
tok.includes('`'));
|
|
106
|
+
}
|
|
59
107
|
/**
|
|
60
108
|
* Strip data-argument values from a shell command.
|
|
61
109
|
*
|
|
@@ -119,6 +167,14 @@ export function stripDataArgs(command) {
|
|
|
119
167
|
positionInSegment = 0;
|
|
120
168
|
continue;
|
|
121
169
|
}
|
|
170
|
+
// Same boundary, but as a bare string — the shape the argv path delivers.
|
|
171
|
+
if (typeof tok === 'string' && SHELL_OPS.has(tok)) {
|
|
172
|
+
out.push(tok);
|
|
173
|
+
segmentTopCmd = null;
|
|
174
|
+
segmentSubCmd = null;
|
|
175
|
+
positionInSegment = 0;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
122
178
|
if (typeof tok !== 'string') {
|
|
123
179
|
// Patterns / comments / other object tokens — preserve literal form.
|
|
124
180
|
out.push(JSON.stringify(tok));
|
|
@@ -143,12 +199,67 @@ export function stripDataArgs(command) {
|
|
|
143
199
|
segmentSubCmd !== null &&
|
|
144
200
|
contextSubcommands.has(segmentSubCmd));
|
|
145
201
|
if (inDataBearingContext) {
|
|
202
|
+
// A heredoc feeding a data-bearing command (`git commit -F - <<EOF`) is
|
|
203
|
+
// MESSAGE TEXT: mask the opening token and everything up to the
|
|
204
|
+
// terminator line so the body never reaches the rule regex. Masking the
|
|
205
|
+
// `-` value alone was not enough — the body arrives as its own tokens.
|
|
206
|
+
//
|
|
207
|
+
// This deliberately lives INSIDE the data-bearing branch: `bash <<EOF` /
|
|
208
|
+
// `python3 <<EOF` are not data-bearing contexts, so an interpreter's
|
|
209
|
+
// heredoc still reaches the regex intact. Masking that body would be an
|
|
210
|
+
// exploitable blind spot, not a false-positive fix.
|
|
211
|
+
const heredoc = HEREDOC_OPEN.exec(tok);
|
|
212
|
+
if (heredoc) {
|
|
213
|
+
const delim = heredoc[2];
|
|
214
|
+
const closes = (t) => t === delim || t.endsWith('\n' + delim);
|
|
215
|
+
// Locate the terminator FIRST, and mask nothing unless we find one.
|
|
216
|
+
// Masking is a detection blind spot by construction, so it must never
|
|
217
|
+
// widen on a malformed input: an unterminated heredoc would otherwise
|
|
218
|
+
// swallow everything after it — `git commit -F - <<EOF` followed by
|
|
219
|
+
// `curl … | bash` would hide the `curl` and defeat the rule.
|
|
220
|
+
//
|
|
221
|
+
// The body starts on the line AFTER the redirect, so an operator is
|
|
222
|
+
// only a boundary while we are still on the opening line: in
|
|
223
|
+
// `… <<EOF && curl … | bash` the pipe-to-shell really executes and must
|
|
224
|
+
// stay visible. Once a newline has gone by we are inside the body,
|
|
225
|
+
// where `|`, `&&`, `;` are prose — the very characters a commit message
|
|
226
|
+
// quoting a dangerous snippet contains, which is the false positive
|
|
227
|
+
// this masking exists to prevent. Only the terminator line ends it.
|
|
228
|
+
let end = -1;
|
|
229
|
+
if (closes(tok)) {
|
|
230
|
+
end = i;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
let inBody = tok.includes('\n');
|
|
234
|
+
for (let j = i + 1; j < tokens.length; j++) {
|
|
235
|
+
const t = tokens[j];
|
|
236
|
+
if (typeof t !== 'string')
|
|
237
|
+
break;
|
|
238
|
+
if (!inBody && SHELL_OPS.has(t))
|
|
239
|
+
break;
|
|
240
|
+
if (closes(t)) {
|
|
241
|
+
end = j;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
if (t.includes('\n'))
|
|
245
|
+
inBody = true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (end !== -1) {
|
|
249
|
+
out.push(PLACEHOLDER);
|
|
250
|
+
i = end;
|
|
251
|
+
positionInSegment++;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
// No terminator → fall through and let the regex see the tokens.
|
|
255
|
+
}
|
|
146
256
|
// -m "msg" / -F file / --message msg / --file path
|
|
147
257
|
if (DATA_FLAGS_VALUE.has(tok)) {
|
|
148
258
|
out.push(tok);
|
|
149
259
|
if (i + 1 < tokens.length) {
|
|
150
260
|
const next = tokens[i + 1];
|
|
151
|
-
|
|
261
|
+
// An executable value is left intact — see isExecutableValue.
|
|
262
|
+
if (typeof next === 'string' && !isExecutableValue(next)) {
|
|
152
263
|
out.push(PLACEHOLDER);
|
|
153
264
|
i++;
|
|
154
265
|
positionInSegment++;
|
|
@@ -160,7 +271,7 @@ export function stripDataArgs(command) {
|
|
|
160
271
|
}
|
|
161
272
|
// --message=msg / --file=path / --body=...
|
|
162
273
|
const equalPrefix = DATA_FLAGS_EQUAL.find((p) => tok.startsWith(p));
|
|
163
|
-
if (equalPrefix) {
|
|
274
|
+
if (equalPrefix && !isExecutableValue(tok.slice(equalPrefix.length))) {
|
|
164
275
|
out.push(equalPrefix + PLACEHOLDER);
|
|
165
276
|
positionInSegment++;
|
|
166
277
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preprocess.js","sourceRoot":"","sources":["../../src/guards-evaluator/preprocess.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"preprocess.js","sourceRoot":"","sources":["../../src/guards-evaluator/preprocess.ts"],"names":[],"mappings":"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"}
|