@vellumai/credential-executor 0.4.55
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/Dockerfile +55 -0
- package/bun.lock +37 -0
- package/package.json +32 -0
- package/src/__tests__/command-executor.test.ts +1333 -0
- package/src/__tests__/command-validator.test.ts +708 -0
- package/src/__tests__/command-workspace.test.ts +997 -0
- package/src/__tests__/grant-store.test.ts +467 -0
- package/src/__tests__/http-executor.test.ts +1251 -0
- package/src/__tests__/http-policy.test.ts +970 -0
- package/src/__tests__/local-materializers.test.ts +826 -0
- package/src/__tests__/managed-materializers.test.ts +961 -0
- package/src/__tests__/toolstore.test.ts +539 -0
- package/src/__tests__/transport.test.ts +388 -0
- package/src/audit/store.ts +188 -0
- package/src/commands/auth-adapters.ts +169 -0
- package/src/commands/executor.ts +840 -0
- package/src/commands/output-scan.ts +157 -0
- package/src/commands/profiles.ts +282 -0
- package/src/commands/validator.ts +438 -0
- package/src/commands/workspace.ts +512 -0
- package/src/grants/index.ts +17 -0
- package/src/grants/persistent-store.ts +247 -0
- package/src/grants/rpc-handlers.ts +269 -0
- package/src/grants/temporary-store.ts +219 -0
- package/src/http/audit.ts +84 -0
- package/src/http/executor.ts +540 -0
- package/src/http/path-template.ts +179 -0
- package/src/http/policy.ts +256 -0
- package/src/http/response-filter.ts +233 -0
- package/src/index.ts +106 -0
- package/src/main.ts +263 -0
- package/src/managed-main.ts +420 -0
- package/src/materializers/local.ts +300 -0
- package/src/materializers/managed-platform.ts +270 -0
- package/src/paths.ts +137 -0
- package/src/server.ts +636 -0
- package/src/subjects/local.ts +177 -0
- package/src/subjects/managed.ts +290 -0
- package/src/toolstore/integrity.ts +94 -0
- package/src/toolstore/manifest.ts +154 -0
- package/src/toolstore/publish.ts +342 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure command manifest validator.
|
|
3
|
+
*
|
|
4
|
+
* Validates that a {@link SecureCommandManifest} meets the CES security
|
|
5
|
+
* invariants before it can be registered. Validation is fail-closed: any
|
|
6
|
+
* structural issue, missing field, or policy violation results in rejection.
|
|
7
|
+
*
|
|
8
|
+
* Invariants enforced:
|
|
9
|
+
*
|
|
10
|
+
* 1. The entrypoint and bundleId must not be a denied binary.
|
|
11
|
+
* 2. At least one command profile must be declared (no empty manifests).
|
|
12
|
+
* 3. Each profile must have at least one allowed argv pattern.
|
|
13
|
+
* 4. Denied subcommands and denied flags lists are checked for consistency.
|
|
14
|
+
* 5. Auth adapter config must be structurally valid.
|
|
15
|
+
* 6. `egressMode` must be explicitly declared.
|
|
16
|
+
* 7. When `egressMode` is `proxy_required`, each profile must declare at
|
|
17
|
+
* least one allowed network target.
|
|
18
|
+
* 8. When `egressMode` is `no_network`, profiles must not declare network
|
|
19
|
+
* targets (contradictory).
|
|
20
|
+
* 9. Overbroad patterns (e.g. a single `<param...>` that matches anything)
|
|
21
|
+
* are rejected.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
validateAuthAdapterConfig,
|
|
26
|
+
} from "./auth-adapters.js";
|
|
27
|
+
import {
|
|
28
|
+
type SecureCommandManifest,
|
|
29
|
+
type CommandProfile,
|
|
30
|
+
type AllowedArgvPattern,
|
|
31
|
+
MANIFEST_SCHEMA_VERSION,
|
|
32
|
+
EGRESS_MODES,
|
|
33
|
+
EgressMode,
|
|
34
|
+
isDeniedBinary,
|
|
35
|
+
pathBasename,
|
|
36
|
+
} from "./profiles.js";
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Validation result
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
export interface ValidationResult {
|
|
43
|
+
/** Whether the manifest passed all checks. */
|
|
44
|
+
valid: boolean;
|
|
45
|
+
/** List of human-readable error messages (empty when valid). */
|
|
46
|
+
errors: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Top-level validator
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Validate a secure command manifest against all CES security invariants.
|
|
55
|
+
*
|
|
56
|
+
* Returns a {@link ValidationResult} with `valid: false` and a list of
|
|
57
|
+
* error messages if any check fails. Validation is exhaustive — all
|
|
58
|
+
* violations are reported, not just the first.
|
|
59
|
+
*/
|
|
60
|
+
export function validateManifest(
|
|
61
|
+
manifest: SecureCommandManifest,
|
|
62
|
+
): ValidationResult {
|
|
63
|
+
const errors: string[] = [];
|
|
64
|
+
|
|
65
|
+
// -- Schema version
|
|
66
|
+
if (manifest.schemaVersion !== MANIFEST_SCHEMA_VERSION) {
|
|
67
|
+
errors.push(
|
|
68
|
+
`Unsupported schema version "${manifest.schemaVersion}". Expected "${MANIFEST_SCHEMA_VERSION}".`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// -- Required string fields
|
|
73
|
+
if (!manifest.bundleDigest || manifest.bundleDigest.trim().length === 0) {
|
|
74
|
+
errors.push("bundleDigest is required and must be non-empty.");
|
|
75
|
+
}
|
|
76
|
+
if (!manifest.bundleId || manifest.bundleId.trim().length === 0) {
|
|
77
|
+
errors.push("bundleId is required and must be non-empty.");
|
|
78
|
+
}
|
|
79
|
+
if (!manifest.version || manifest.version.trim().length === 0) {
|
|
80
|
+
errors.push("version is required and must be non-empty.");
|
|
81
|
+
}
|
|
82
|
+
if (!manifest.entrypoint || manifest.entrypoint.trim().length === 0) {
|
|
83
|
+
errors.push("entrypoint is required and must be non-empty.");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// -- Denied binary check (entrypoint basename and bundleId)
|
|
87
|
+
if (manifest.entrypoint && isDeniedBinary(manifest.entrypoint)) {
|
|
88
|
+
errors.push(
|
|
89
|
+
`Entrypoint "${manifest.entrypoint}" (basename: "${pathBasename(manifest.entrypoint)}") is a structurally denied binary. ` +
|
|
90
|
+
`Generic HTTP clients, interpreters, and shell trampolines cannot be secure command profiles.`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (manifest.bundleId && isDeniedBinary(manifest.bundleId)) {
|
|
94
|
+
errors.push(
|
|
95
|
+
`bundleId "${manifest.bundleId}" matches a structurally denied binary name. ` +
|
|
96
|
+
`Generic HTTP clients, interpreters, and shell trampolines cannot be secure command profiles.`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// -- Egress mode
|
|
101
|
+
if (!manifest.egressMode) {
|
|
102
|
+
errors.push(
|
|
103
|
+
`egressMode is required. Valid values: ${EGRESS_MODES.join(", ")}.`,
|
|
104
|
+
);
|
|
105
|
+
} else if (!(EGRESS_MODES as readonly string[]).includes(manifest.egressMode)) {
|
|
106
|
+
errors.push(
|
|
107
|
+
`Invalid egressMode "${manifest.egressMode}". Valid values: ${EGRESS_MODES.join(", ")}.`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// -- Auth adapter
|
|
112
|
+
if (!manifest.authAdapter) {
|
|
113
|
+
errors.push("authAdapter is required.");
|
|
114
|
+
} else {
|
|
115
|
+
const adapterErrors = validateAuthAdapterConfig(manifest.authAdapter);
|
|
116
|
+
for (const e of adapterErrors) {
|
|
117
|
+
errors.push(`authAdapter: ${e}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// -- Command profiles (must have at least one)
|
|
122
|
+
if (
|
|
123
|
+
!manifest.commandProfiles ||
|
|
124
|
+
Object.keys(manifest.commandProfiles).length === 0
|
|
125
|
+
) {
|
|
126
|
+
errors.push(
|
|
127
|
+
"At least one command profile must be declared. " +
|
|
128
|
+
"Secure command profiles cannot default to 'run any subcommand on this binary.'",
|
|
129
|
+
);
|
|
130
|
+
} else {
|
|
131
|
+
for (const [profileName, profile] of Object.entries(
|
|
132
|
+
manifest.commandProfiles,
|
|
133
|
+
)) {
|
|
134
|
+
const profileErrors = validateProfile(
|
|
135
|
+
profileName,
|
|
136
|
+
profile,
|
|
137
|
+
manifest.egressMode,
|
|
138
|
+
);
|
|
139
|
+
errors.push(...profileErrors);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
valid: errors.length === 0,
|
|
145
|
+
errors,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// Profile-level validation
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
function validateProfile(
|
|
154
|
+
profileName: string,
|
|
155
|
+
profile: CommandProfile,
|
|
156
|
+
egressMode: EgressMode | undefined,
|
|
157
|
+
): string[] {
|
|
158
|
+
const errors: string[] = [];
|
|
159
|
+
const prefix = `Profile "${profileName}"`;
|
|
160
|
+
|
|
161
|
+
// -- Description
|
|
162
|
+
if (!profile.description || profile.description.trim().length === 0) {
|
|
163
|
+
errors.push(`${prefix}: description is required and must be non-empty.`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// -- Allowed argv patterns (must have at least one)
|
|
167
|
+
if (
|
|
168
|
+
!profile.allowedArgvPatterns ||
|
|
169
|
+
profile.allowedArgvPatterns.length === 0
|
|
170
|
+
) {
|
|
171
|
+
errors.push(
|
|
172
|
+
`${prefix}: at least one allowedArgvPattern is required. ` +
|
|
173
|
+
"Profiles must explicitly declare what invocations are allowed.",
|
|
174
|
+
);
|
|
175
|
+
} else {
|
|
176
|
+
for (const pattern of profile.allowedArgvPatterns) {
|
|
177
|
+
const patternErrors = validateArgvPattern(prefix, pattern);
|
|
178
|
+
errors.push(...patternErrors);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// -- Denied subcommands (optional but must be an array)
|
|
183
|
+
if (profile.deniedSubcommands) {
|
|
184
|
+
for (const sub of profile.deniedSubcommands) {
|
|
185
|
+
if (!sub || sub.trim().length === 0) {
|
|
186
|
+
errors.push(
|
|
187
|
+
`${prefix}: deniedSubcommands contains an empty string.`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// -- Denied flags (optional)
|
|
194
|
+
if (profile.deniedFlags) {
|
|
195
|
+
for (const flag of profile.deniedFlags) {
|
|
196
|
+
if (!flag || flag.trim().length === 0) {
|
|
197
|
+
errors.push(`${prefix}: deniedFlags contains an empty string.`);
|
|
198
|
+
}
|
|
199
|
+
if (flag && !flag.startsWith("-")) {
|
|
200
|
+
errors.push(
|
|
201
|
+
`${prefix}: deniedFlags entry "${flag}" does not start with "-". ` +
|
|
202
|
+
"Flags must start with a dash.",
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// -- Network targets vs egress mode consistency
|
|
209
|
+
if (egressMode === EgressMode.ProxyRequired) {
|
|
210
|
+
if (
|
|
211
|
+
!profile.allowedNetworkTargets ||
|
|
212
|
+
profile.allowedNetworkTargets.length === 0
|
|
213
|
+
) {
|
|
214
|
+
errors.push(
|
|
215
|
+
`${prefix}: egressMode is "proxy_required" but no allowedNetworkTargets are declared. ` +
|
|
216
|
+
"Commands with network egress must declare their allowed network targets.",
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (egressMode === EgressMode.NoNetwork) {
|
|
222
|
+
if (
|
|
223
|
+
profile.allowedNetworkTargets &&
|
|
224
|
+
profile.allowedNetworkTargets.length > 0
|
|
225
|
+
) {
|
|
226
|
+
errors.push(
|
|
227
|
+
`${prefix}: egressMode is "no_network" but allowedNetworkTargets are declared. ` +
|
|
228
|
+
"This is contradictory — remove network targets or change egressMode.",
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return errors;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
// Argv pattern validation
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
function validateArgvPattern(
|
|
241
|
+
profilePrefix: string,
|
|
242
|
+
pattern: AllowedArgvPattern,
|
|
243
|
+
): string[] {
|
|
244
|
+
const errors: string[] = [];
|
|
245
|
+
|
|
246
|
+
if (!pattern.name || pattern.name.trim().length === 0) {
|
|
247
|
+
errors.push(
|
|
248
|
+
`${profilePrefix}: argv pattern has no name. Each pattern must be named for audit logging.`,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!pattern.tokens || pattern.tokens.length === 0) {
|
|
253
|
+
errors.push(
|
|
254
|
+
`${profilePrefix}: argv pattern "${pattern.name}" has no tokens. ` +
|
|
255
|
+
"Empty patterns would match any invocation.",
|
|
256
|
+
);
|
|
257
|
+
return errors;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Check for overbroad patterns: a single rest placeholder matches anything
|
|
261
|
+
if (
|
|
262
|
+
pattern.tokens.length === 1 &&
|
|
263
|
+
isRestPlaceholder(pattern.tokens[0]!)
|
|
264
|
+
) {
|
|
265
|
+
errors.push(
|
|
266
|
+
`${profilePrefix}: argv pattern "${pattern.name}" contains only a rest placeholder ` +
|
|
267
|
+
`("${pattern.tokens[0]}"). This would match any invocation and is too broad.`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Rest placeholder must be last token
|
|
272
|
+
for (let i = 0; i < pattern.tokens.length; i++) {
|
|
273
|
+
const token = pattern.tokens[i]!;
|
|
274
|
+
if (isRestPlaceholder(token) && i < pattern.tokens.length - 1) {
|
|
275
|
+
errors.push(
|
|
276
|
+
`${profilePrefix}: argv pattern "${pattern.name}" has a rest placeholder ` +
|
|
277
|
+
`("${token}") at position ${i}, but rest placeholders must be the last token.`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return errors;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
// Argv matching (used by the runtime to check commands against profiles)
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Returns true if the token is a single-value placeholder like `<name>`.
|
|
291
|
+
*/
|
|
292
|
+
function isPlaceholder(token: string): boolean {
|
|
293
|
+
return token.startsWith("<") && token.endsWith(">") && !token.endsWith("...>");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Returns true if the token is a rest placeholder like `<name...>`.
|
|
298
|
+
*/
|
|
299
|
+
function isRestPlaceholder(token: string): boolean {
|
|
300
|
+
return token.startsWith("<") && token.endsWith("...>");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Check if a concrete argv array matches an allowed argv pattern.
|
|
305
|
+
*
|
|
306
|
+
* Matching rules:
|
|
307
|
+
* - Literal tokens must match exactly.
|
|
308
|
+
* - `<name>` matches exactly one argument.
|
|
309
|
+
* - `<name...>` matches one or more remaining arguments (must be last token).
|
|
310
|
+
*/
|
|
311
|
+
export function matchesArgvPattern(
|
|
312
|
+
argv: readonly string[],
|
|
313
|
+
pattern: AllowedArgvPattern,
|
|
314
|
+
): boolean {
|
|
315
|
+
const { tokens } = pattern;
|
|
316
|
+
|
|
317
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
318
|
+
const token = tokens[i]!;
|
|
319
|
+
|
|
320
|
+
if (isRestPlaceholder(token)) {
|
|
321
|
+
// Rest placeholder: must have at least one remaining arg
|
|
322
|
+
return argv.length > i;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// No more args but still have pattern tokens
|
|
326
|
+
if (i >= argv.length) return false;
|
|
327
|
+
|
|
328
|
+
if (isPlaceholder(token)) {
|
|
329
|
+
// Single placeholder: matches any single value
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Literal: must match exactly
|
|
334
|
+
if (argv[i] !== token) return false;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// All pattern tokens consumed — argv must also be fully consumed
|
|
338
|
+
return argv.length === tokens.length;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
// Full command validation against a manifest
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
export interface CommandValidationResult {
|
|
346
|
+
/** Whether the command is allowed. */
|
|
347
|
+
allowed: boolean;
|
|
348
|
+
/** The profile name that matched (undefined when rejected). */
|
|
349
|
+
matchedProfile?: string;
|
|
350
|
+
/** The pattern name that matched (undefined when rejected). */
|
|
351
|
+
matchedPattern?: string;
|
|
352
|
+
/** Human-readable reason for rejection (undefined when allowed). */
|
|
353
|
+
reason?: string;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Validate a concrete command invocation (argv array) against a manifest.
|
|
358
|
+
*
|
|
359
|
+
* Checks:
|
|
360
|
+
* 1. The argv is non-empty.
|
|
361
|
+
* 2. The argv does not contain any denied subcommands (across all profiles).
|
|
362
|
+
* 3. The argv does not contain any denied flags (across all profiles).
|
|
363
|
+
* 4. At least one profile's allowed argv patterns matches.
|
|
364
|
+
*
|
|
365
|
+
* This function does NOT re-validate the manifest itself — call
|
|
366
|
+
* {@link validateManifest} separately during registration.
|
|
367
|
+
*/
|
|
368
|
+
export function validateCommand(
|
|
369
|
+
manifest: SecureCommandManifest,
|
|
370
|
+
argv: readonly string[],
|
|
371
|
+
): CommandValidationResult {
|
|
372
|
+
if (argv.length === 0) {
|
|
373
|
+
return {
|
|
374
|
+
allowed: false,
|
|
375
|
+
reason: "Empty argv — no command to validate.",
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Collect all denied subcommands and flags across profiles
|
|
380
|
+
const allDeniedSubcommands = new Set<string>();
|
|
381
|
+
const allDeniedFlags = new Set<string>();
|
|
382
|
+
|
|
383
|
+
for (const profile of Object.values(manifest.commandProfiles)) {
|
|
384
|
+
for (const sub of profile.deniedSubcommands) {
|
|
385
|
+
allDeniedSubcommands.add(sub);
|
|
386
|
+
}
|
|
387
|
+
if (profile.deniedFlags) {
|
|
388
|
+
for (const flag of profile.deniedFlags) {
|
|
389
|
+
allDeniedFlags.add(flag);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Check denied subcommands (match against first N tokens of argv)
|
|
395
|
+
for (const denied of allDeniedSubcommands) {
|
|
396
|
+
const deniedParts = denied.split(/\s+/);
|
|
397
|
+
if (deniedParts.length <= argv.length) {
|
|
398
|
+
const match = deniedParts.every((part, i) => argv[i] === part);
|
|
399
|
+
if (match) {
|
|
400
|
+
return {
|
|
401
|
+
allowed: false,
|
|
402
|
+
reason: `Subcommand "${denied}" is explicitly denied.`,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Check denied flags
|
|
409
|
+
for (const arg of argv) {
|
|
410
|
+
if (allDeniedFlags.has(arg)) {
|
|
411
|
+
return {
|
|
412
|
+
allowed: false,
|
|
413
|
+
reason: `Flag "${arg}" is explicitly denied.`,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Try to match against allowed argv patterns in each profile
|
|
419
|
+
for (const [profileName, profile] of Object.entries(
|
|
420
|
+
manifest.commandProfiles,
|
|
421
|
+
)) {
|
|
422
|
+
for (const pattern of profile.allowedArgvPatterns) {
|
|
423
|
+
if (matchesArgvPattern(argv, pattern)) {
|
|
424
|
+
return {
|
|
425
|
+
allowed: true,
|
|
426
|
+
matchedProfile: profileName,
|
|
427
|
+
matchedPattern: pattern.name,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
allowed: false,
|
|
435
|
+
reason:
|
|
436
|
+
"Command argv does not match any allowed pattern in any profile.",
|
|
437
|
+
};
|
|
438
|
+
}
|