@blogic-cz/agent-tools 0.14.13 → 0.14.15
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/package.json +1 -1
- package/schemas/agent-tools.schema.json +19 -1
- package/src/config/index.ts +3 -0
- package/src/config/loader.ts +4 -0
- package/src/config/types.ts +8 -0
- package/src/db-tool/security.ts +12 -1
- package/src/db-tool/service.ts +26 -8
- package/src/db-tool/types.ts +4 -0
- package/src/k8s-tool/service.ts +0 -1
package/package.json
CHANGED
|
@@ -243,6 +243,16 @@
|
|
|
243
243
|
"vpn": {
|
|
244
244
|
"type": "string",
|
|
245
245
|
"description": "Convenience sugar for a VPN prerequisite key. Runtime normalizes this to prerequisites."
|
|
246
|
+
},
|
|
247
|
+
"allowedMutations": {
|
|
248
|
+
"description": "Explicitly allowed SQL mutation operations per environment. Non-local environments default to read-only.",
|
|
249
|
+
"type": "object",
|
|
250
|
+
"additionalProperties": {
|
|
251
|
+
"type": "array",
|
|
252
|
+
"items": {
|
|
253
|
+
"$ref": "#/definitions/DbMutationOperation"
|
|
254
|
+
}
|
|
255
|
+
}
|
|
246
256
|
}
|
|
247
257
|
},
|
|
248
258
|
"required": ["environments"]
|
|
@@ -538,6 +548,11 @@
|
|
|
538
548
|
}
|
|
539
549
|
},
|
|
540
550
|
"required": ["name"]
|
|
551
|
+
},
|
|
552
|
+
"DbMutationOperation": {
|
|
553
|
+
"description": "SQL mutation operation that can be explicitly allowed for a database environment.",
|
|
554
|
+
"type": "string",
|
|
555
|
+
"enum": ["insert", "update", "delete"]
|
|
541
556
|
}
|
|
542
557
|
},
|
|
543
558
|
"examples": [
|
|
@@ -584,7 +599,10 @@
|
|
|
584
599
|
"namespace": "system"
|
|
585
600
|
},
|
|
586
601
|
"tunnelTimeoutMs": 5000,
|
|
587
|
-
"remotePort": 5432
|
|
602
|
+
"remotePort": 5432,
|
|
603
|
+
"allowedMutations": {
|
|
604
|
+
"test": ["insert"]
|
|
605
|
+
}
|
|
588
606
|
}
|
|
589
607
|
},
|
|
590
608
|
"logs": {
|
package/src/config/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type {
|
|
|
3
3
|
AzureConfig,
|
|
4
4
|
K8sConfig,
|
|
5
5
|
DbEnvConfig,
|
|
6
|
+
DbMutationOperation,
|
|
6
7
|
DatabaseConfig,
|
|
7
8
|
ObservabilityConfig,
|
|
8
9
|
ObservabilityEnvTarget,
|
|
@@ -13,6 +14,8 @@ export type {
|
|
|
13
14
|
GitHubRepoConfig,
|
|
14
15
|
} from "./types";
|
|
15
16
|
|
|
17
|
+
export { DbMutationOperationSchema } from "./types";
|
|
18
|
+
|
|
16
19
|
export {
|
|
17
20
|
ConfigService,
|
|
18
21
|
ConfigServiceLayer,
|
package/src/config/loader.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { dirname } from "node:path";
|
|
|
3
3
|
import { Context, Data, Effect, Layer, Schema } from "effect";
|
|
4
4
|
|
|
5
5
|
import type { AgentToolsConfig, GitHubRepoConfig } from "./types";
|
|
6
|
+
import { DbMutationOperationSchema } from "./types";
|
|
6
7
|
|
|
7
8
|
const CliToolOverrideSchema = Schema.Struct({
|
|
8
9
|
tool: Schema.String,
|
|
@@ -94,6 +95,9 @@ const DbEnvConfigSchema = Schema.Struct({
|
|
|
94
95
|
|
|
95
96
|
const DatabaseConfigSchema = Schema.Struct({
|
|
96
97
|
environments: Schema.Record(Schema.String, DbEnvConfigSchema),
|
|
98
|
+
allowedMutations: Schema.optionalKey(
|
|
99
|
+
Schema.Record(Schema.String, Schema.Array(DbMutationOperationSchema)),
|
|
100
|
+
),
|
|
97
101
|
kubectl: Schema.optionalKey(
|
|
98
102
|
Schema.Struct({
|
|
99
103
|
kubeconfig: Schema.optionalKey(Schema.String),
|
package/src/config/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
1
3
|
/** Azure DevOps profile configuration */
|
|
2
4
|
export type AzureConfig = {
|
|
3
5
|
organization: string;
|
|
@@ -83,10 +85,16 @@ export type DbEnvConfig = {
|
|
|
83
85
|
passwordEnvVar?: string;
|
|
84
86
|
};
|
|
85
87
|
|
|
88
|
+
/** SQL mutation operation that can be explicitly allowed for a database environment. */
|
|
89
|
+
export const DbMutationOperationSchema = Schema.Literals(["insert", "update", "delete"]);
|
|
90
|
+
export type DbMutationOperation = Schema.Schema.Type<typeof DbMutationOperationSchema>;
|
|
91
|
+
|
|
86
92
|
/** Database profile configuration */
|
|
87
93
|
export type DatabaseConfig = ProfilePrerequisites & {
|
|
88
94
|
/** Named database environments, e.g. { local: {...}, test: {...}, prod: {...} } */
|
|
89
95
|
environments: Record<string, DbEnvConfig>;
|
|
96
|
+
/** Explicitly allowed SQL mutation operations per environment. Non-local environments default to read-only. */
|
|
97
|
+
allowedMutations?: Record<string, readonly DbMutationOperation[]>;
|
|
90
98
|
kubectl?: {
|
|
91
99
|
/** Optional kubeconfig path. Supports ${ENV_VAR} templates. */
|
|
92
100
|
kubeconfig?: string;
|
package/src/db-tool/security.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SchemaErrorInfo } from "./types";
|
|
1
|
+
import type { DbMutationOperation, SchemaErrorInfo } from "./types";
|
|
2
2
|
|
|
3
3
|
const MUTATION_PATTERNS = [
|
|
4
4
|
/^\s*UPDATE\s+/i,
|
|
@@ -10,6 +10,12 @@ const MUTATION_PATTERNS = [
|
|
|
10
10
|
/^\s*CREATE\s+/i,
|
|
11
11
|
];
|
|
12
12
|
|
|
13
|
+
const ALLOWABLE_MUTATION_PATTERNS: Array<[DbMutationOperation, RegExp]> = [
|
|
14
|
+
["insert", /^\s*INSERT\s+/i],
|
|
15
|
+
["update", /^\s*UPDATE\s+/i],
|
|
16
|
+
["delete", /^\s*DELETE\s+/i],
|
|
17
|
+
];
|
|
18
|
+
|
|
13
19
|
const TABLE_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)?$/;
|
|
14
20
|
|
|
15
21
|
/**
|
|
@@ -78,6 +84,11 @@ export function isMutationQuery(sql: string): boolean {
|
|
|
78
84
|
return MUTATION_PATTERNS.some((pattern) => pattern.test(stripped));
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
export function getAllowedMutationOperation(sql: string): DbMutationOperation | undefined {
|
|
88
|
+
const stripped = stripSqlComments(sql);
|
|
89
|
+
return ALLOWABLE_MUTATION_PATTERNS.find(([, pattern]) => pattern.test(stripped))?.[0];
|
|
90
|
+
}
|
|
91
|
+
|
|
81
92
|
export function isValidTableName(tableName: string): boolean {
|
|
82
93
|
return TABLE_NAME_PATTERN.test(tableName);
|
|
83
94
|
}
|
package/src/db-tool/service.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
2
2
|
import { Clock, Context, Duration, Effect, Layer, Ref, Stream } from "effect";
|
|
3
3
|
|
|
4
|
-
import type { DbConfig, QueryResult, SchemaMode } from "./types";
|
|
4
|
+
import type { DbConfig, DbMutationOperation, QueryResult, SchemaMode } from "./types";
|
|
5
5
|
|
|
6
6
|
import { ConfigService } from "#config";
|
|
7
7
|
import { isPrerequisiteRunError } from "#shared/prerequisites/errors";
|
|
@@ -23,7 +23,12 @@ import {
|
|
|
23
23
|
parseTableReference,
|
|
24
24
|
SYSTEM_SCHEMAS_SQL,
|
|
25
25
|
} from "./schema";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
detectSchemaError,
|
|
28
|
+
getAllowedMutationOperation,
|
|
29
|
+
isValidTableName,
|
|
30
|
+
isMutationQuery,
|
|
31
|
+
} from "./security";
|
|
27
32
|
import { transformQueryResult } from "./transformers";
|
|
28
33
|
|
|
29
34
|
const LOCALHOST_HOSTS = new Set(["localhost", "127.0.0.1"]);
|
|
@@ -32,7 +37,8 @@ export function resolveDbAccessMode(
|
|
|
32
37
|
env: string,
|
|
33
38
|
host: string,
|
|
34
39
|
hasKubectlConfig: boolean,
|
|
35
|
-
|
|
40
|
+
allowedMutations: readonly DbMutationOperation[] = [],
|
|
41
|
+
): Pick<DbConfig, "allowMutations" | "allowedMutations" | "host" | "needsTunnel"> {
|
|
36
42
|
const isLocalHost = LOCALHOST_HOSTS.has(host);
|
|
37
43
|
const isLocalEnvironment = env === "local";
|
|
38
44
|
|
|
@@ -40,6 +46,7 @@ export function resolveDbAccessMode(
|
|
|
40
46
|
host,
|
|
41
47
|
needsTunnel: hasKubectlConfig && !isLocalEnvironment && isLocalHost,
|
|
42
48
|
allowMutations: isLocalEnvironment,
|
|
49
|
+
allowedMutations: isLocalEnvironment ? ["insert", "update", "delete"] : allowedMutations,
|
|
43
50
|
};
|
|
44
51
|
}
|
|
45
52
|
|
|
@@ -228,7 +235,6 @@ export class DbService extends Context.Service<
|
|
|
228
235
|
dbConfig,
|
|
229
236
|
(command, _label) => executeShellCommand(command),
|
|
230
237
|
effect,
|
|
231
|
-
{ tryWithoutPrerequisites: true },
|
|
232
238
|
).pipe(
|
|
233
239
|
Effect.mapError((error) =>
|
|
234
240
|
isPrerequisiteRunError(error)
|
|
@@ -640,6 +646,7 @@ export class DbService extends Context.Service<
|
|
|
640
646
|
env,
|
|
641
647
|
envConfig.host,
|
|
642
648
|
dbConfig.kubectl !== undefined,
|
|
649
|
+
dbConfig.allowedMutations?.[env] ?? [],
|
|
643
650
|
);
|
|
644
651
|
|
|
645
652
|
return {
|
|
@@ -651,6 +658,7 @@ export class DbService extends Context.Service<
|
|
|
651
658
|
port: envConfig.port,
|
|
652
659
|
needsTunnel: accessMode.needsTunnel,
|
|
653
660
|
allowMutations: accessMode.allowMutations,
|
|
661
|
+
allowedMutations: accessMode.allowedMutations,
|
|
654
662
|
};
|
|
655
663
|
};
|
|
656
664
|
|
|
@@ -663,12 +671,22 @@ export class DbService extends Context.Service<
|
|
|
663
671
|
const resolvedConfig = yield* resolveDbConfig(config, env);
|
|
664
672
|
const password = yield* resolvePassword(resolvedConfig, env);
|
|
665
673
|
const mutation = isMutationQuery(sql);
|
|
666
|
-
|
|
667
|
-
|
|
674
|
+
const mutationOperation = mutation ? getAllowedMutationOperation(sql) : undefined;
|
|
675
|
+
const mutationAllowed =
|
|
676
|
+
!mutation ||
|
|
677
|
+
resolvedConfig.allowMutations ||
|
|
678
|
+
(mutationOperation !== undefined &&
|
|
679
|
+
resolvedConfig.allowedMutations.includes(mutationOperation));
|
|
680
|
+
|
|
681
|
+
if (!mutationAllowed) {
|
|
682
|
+
const allowed =
|
|
683
|
+
resolvedConfig.allowedMutations.length > 0
|
|
684
|
+
? resolvedConfig.allowedMutations.join(", ")
|
|
685
|
+
: "none";
|
|
668
686
|
return yield* new DbMutationBlockedError({
|
|
669
|
-
message:
|
|
670
|
-
"Mutation queries (UPDATE, INSERT, DELETE, etc.) are not allowed on this environment. Use a local environment for mutations.",
|
|
687
|
+
message: `Mutation queries are not allowed on environment ${env}. Allowed mutation operations: ${allowed}.`,
|
|
671
688
|
environment: env,
|
|
689
|
+
hint: 'Configure database.<profile>.allowedMutations.<env> with explicit operations such as ["insert"] if this environment should allow controlled mutations.',
|
|
672
690
|
});
|
|
673
691
|
}
|
|
674
692
|
|
package/src/db-tool/types.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { DbMutationOperation } from "#config";
|
|
1
2
|
import type { Environment, OutputFormat } from "#shared";
|
|
3
|
+
|
|
4
|
+
export type { DbMutationOperation };
|
|
2
5
|
export type { Environment, OutputFormat };
|
|
3
6
|
|
|
4
7
|
export type SchemaMode = "tables" | "columns" | "full" | "relationships";
|
|
@@ -12,6 +15,7 @@ export type DbConfig = {
|
|
|
12
15
|
port: number;
|
|
13
16
|
needsTunnel: boolean;
|
|
14
17
|
allowMutations: boolean;
|
|
18
|
+
allowedMutations: readonly DbMutationOperation[];
|
|
15
19
|
};
|
|
16
20
|
|
|
17
21
|
export type QueryResult = {
|