timonel 3.0.0 → 3.1.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +432 -1
  3. package/SECURITY.md +2 -2
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.js +2 -0
  6. package/dist/lib/policy/configurationLoader.d.ts +46 -0
  7. package/dist/lib/policy/configurationLoader.js +251 -0
  8. package/dist/lib/policy/errorContextGenerator.d.ts +63 -0
  9. package/dist/lib/policy/errorContextGenerator.js +302 -0
  10. package/dist/lib/policy/errors.d.ts +40 -0
  11. package/dist/lib/policy/errors.js +109 -0
  12. package/dist/lib/policy/index.d.ts +11 -0
  13. package/dist/lib/policy/index.js +10 -0
  14. package/dist/lib/policy/parallelExecutor.d.ts +58 -0
  15. package/dist/lib/policy/parallelExecutor.js +215 -0
  16. package/dist/lib/policy/pluginLoader.d.ts +41 -0
  17. package/dist/lib/policy/pluginLoader.js +220 -0
  18. package/dist/lib/policy/pluginRegistry.d.ts +14 -0
  19. package/dist/lib/policy/pluginRegistry.js +69 -0
  20. package/dist/lib/policy/policyEngine.d.ts +39 -0
  21. package/dist/lib/policy/policyEngine.js +495 -0
  22. package/dist/lib/policy/resultAggregator.d.ts +8 -0
  23. package/dist/lib/policy/resultAggregator.js +138 -0
  24. package/dist/lib/policy/resultFormatter.d.ts +25 -0
  25. package/dist/lib/policy/resultFormatter.js +217 -0
  26. package/dist/lib/policy/types.d.ts +111 -0
  27. package/dist/lib/policy/types.js +1 -0
  28. package/dist/lib/policy/validationCache.d.ts +58 -0
  29. package/dist/lib/policy/validationCache.js +289 -0
  30. package/dist/lib/rutter.d.ts +7 -2
  31. package/dist/lib/rutter.js +177 -8
  32. package/dist/lib/templates/flexible-subchart.js +4 -2
  33. package/dist/lib/templates/umbrella-chart.js +8 -8
  34. package/dist/lib/umbrellaRutter.d.ts +1 -1
  35. package/dist/lib/umbrellaRutter.js +2 -2
  36. package/dist/lib/validation/inputValidator.d.ts +26 -0
  37. package/dist/lib/validation/inputValidator.js +176 -0
  38. package/dist/types/index.d.ts +27 -0
  39. package/dist/types/index.js +1 -0
  40. package/package.json +22 -20
@@ -0,0 +1,26 @@
1
+ import type { PolicyConfig, PolicyContext, PolicyRule, PluginConfig } from '../../types/index.js';
2
+ export interface ValidationOptions {
3
+ maxStringLength?: number;
4
+ maxArrayLength?: number;
5
+ maxObjectDepth?: number;
6
+ allowedProtocols?: string[];
7
+ sanitizeStrings?: boolean;
8
+ }
9
+ export declare class InputValidator {
10
+ private options;
11
+ constructor(options?: ValidationOptions);
12
+ validatePolicyConfig(config: unknown): PolicyConfig;
13
+ validatePolicyRule(rule: unknown): PolicyRule;
14
+ validatePolicyContext(context: unknown): PolicyContext;
15
+ validatePluginConfig(config: unknown): PluginConfig;
16
+ private validateStringLength;
17
+ private validateArrayLength;
18
+ private validateObjectDepth;
19
+ private sanitizeString;
20
+ private sanitizeObject;
21
+ }
22
+ export declare const defaultValidator: InputValidator;
23
+ export declare const validatePolicyConfig: (config: unknown) => PolicyConfig;
24
+ export declare const validatePolicyRule: (rule: unknown) => PolicyRule;
25
+ export declare const validatePolicyContext: (context: unknown) => PolicyContext;
26
+ export declare const validatePluginConfig: (config: unknown) => PluginConfig;
@@ -0,0 +1,176 @@
1
+ import { PolicyValidationError } from '../policy/errors.js';
2
+ const DEFAULT_OPTIONS = {
3
+ maxStringLength: 10000,
4
+ maxArrayLength: 1000,
5
+ maxObjectDepth: 10,
6
+ allowedProtocols: ['http', 'https'],
7
+ sanitizeStrings: true,
8
+ };
9
+ export class InputValidator {
10
+ constructor(options = {}) {
11
+ this.options = { ...DEFAULT_OPTIONS, ...options };
12
+ }
13
+ validatePolicyConfig(config) {
14
+ if (!config || typeof config !== 'object') {
15
+ throw new PolicyValidationError('PolicyConfig must be a non-null object');
16
+ }
17
+ const typedConfig = config;
18
+ if (!typedConfig.id || typeof typedConfig.id !== 'string') {
19
+ throw new PolicyValidationError('PolicyConfig.id must be a non-empty string');
20
+ }
21
+ if (!typedConfig.name || typeof typedConfig.name !== 'string') {
22
+ throw new PolicyValidationError('PolicyConfig.name must be a non-empty string');
23
+ }
24
+ if (!Array.isArray(typedConfig.rules)) {
25
+ throw new PolicyValidationError('PolicyConfig.rules must be an array');
26
+ }
27
+ this.validateStringLength(typedConfig.id, 'PolicyConfig.id');
28
+ this.validateStringLength(typedConfig.name, 'PolicyConfig.name');
29
+ this.validateArrayLength(typedConfig.rules, 'PolicyConfig.rules');
30
+ const validatedRules = typedConfig.rules.map((rule, index) => {
31
+ try {
32
+ return this.validatePolicyRule(rule);
33
+ }
34
+ catch (error) {
35
+ throw new PolicyValidationError(`PolicyConfig.rules[${index}]: ${error.message}`);
36
+ }
37
+ });
38
+ const sanitizedConfig = {
39
+ id: this.sanitizeString(typedConfig.id),
40
+ name: this.sanitizeString(typedConfig.name),
41
+ rules: validatedRules,
42
+ enabled: typeof typedConfig.enabled === 'boolean' ? typedConfig.enabled : true,
43
+ };
44
+ if (typedConfig.description) {
45
+ sanitizedConfig.description = this.sanitizeString(typedConfig.description);
46
+ }
47
+ if (typedConfig.version) {
48
+ sanitizedConfig.version = this.sanitizeString(typedConfig.version);
49
+ }
50
+ return sanitizedConfig;
51
+ }
52
+ validatePolicyRule(rule) {
53
+ if (!rule || typeof rule !== 'object') {
54
+ throw new PolicyValidationError('PolicyRule must be a non-null object');
55
+ }
56
+ const typedRule = rule;
57
+ if (!typedRule.id || typeof typedRule.id !== 'string') {
58
+ throw new PolicyValidationError('PolicyRule.id must be a non-empty string');
59
+ }
60
+ if (!typedRule.type || typeof typedRule.type !== 'string') {
61
+ throw new PolicyValidationError('PolicyRule.type must be a non-empty string');
62
+ }
63
+ if (!typedRule.condition || typeof typedRule.condition !== 'object') {
64
+ throw new PolicyValidationError('PolicyRule.condition must be an object');
65
+ }
66
+ this.validateStringLength(typedRule.id, 'PolicyRule.id');
67
+ this.validateStringLength(typedRule.type, 'PolicyRule.type');
68
+ this.validateObjectDepth(typedRule.condition, 'PolicyRule.condition');
69
+ const sanitizedRule = {
70
+ id: this.sanitizeString(typedRule.id),
71
+ type: this.sanitizeString(typedRule.type),
72
+ condition: this.sanitizeObject(typedRule.condition),
73
+ priority: typeof typedRule.priority === 'number' ? typedRule.priority : 0,
74
+ enabled: typeof typedRule.enabled === 'boolean' ? typedRule.enabled : true,
75
+ };
76
+ if (typedRule.action) {
77
+ sanitizedRule.action = this.sanitizeString(typedRule.action);
78
+ }
79
+ return sanitizedRule;
80
+ }
81
+ validatePolicyContext(context) {
82
+ if (!context || typeof context !== 'object') {
83
+ throw new PolicyValidationError('PolicyContext must be a non-null object');
84
+ }
85
+ const typedContext = context;
86
+ this.validateObjectDepth(typedContext, 'PolicyContext');
87
+ return this.sanitizeObject(typedContext);
88
+ }
89
+ validatePluginConfig(config) {
90
+ if (!config || typeof config !== 'object') {
91
+ throw new PolicyValidationError('PluginConfig must be a non-null object');
92
+ }
93
+ const typedConfig = config;
94
+ if (!typedConfig.name || typeof typedConfig.name !== 'string') {
95
+ throw new PolicyValidationError('PluginConfig.name must be a non-empty string');
96
+ }
97
+ this.validateStringLength(typedConfig.name, 'PluginConfig.name');
98
+ if (typedConfig.config && typeof typedConfig.config === 'object') {
99
+ this.validateObjectDepth(typedConfig.config, 'PluginConfig.config');
100
+ }
101
+ const sanitizedConfig = {
102
+ name: this.sanitizeString(typedConfig.name),
103
+ enabled: typeof typedConfig.enabled === 'boolean' ? typedConfig.enabled : true,
104
+ };
105
+ if (typedConfig.version) {
106
+ sanitizedConfig.version = this.sanitizeString(typedConfig.version);
107
+ }
108
+ if (typedConfig.config) {
109
+ sanitizedConfig.config = this.sanitizeObject(typedConfig.config);
110
+ }
111
+ return sanitizedConfig;
112
+ }
113
+ validateStringLength(value, fieldName) {
114
+ if (value.length > this.options.maxStringLength) {
115
+ throw new PolicyValidationError(`${fieldName} exceeds maximum length of ${this.options.maxStringLength} characters`);
116
+ }
117
+ }
118
+ validateArrayLength(value, fieldName) {
119
+ if (value.length > this.options.maxArrayLength) {
120
+ throw new PolicyValidationError(`${fieldName} exceeds maximum length of ${this.options.maxArrayLength} items`);
121
+ }
122
+ }
123
+ validateObjectDepth(obj, fieldName, currentDepth = 0) {
124
+ if (currentDepth > this.options.maxObjectDepth) {
125
+ throw new PolicyValidationError(`${fieldName} exceeds maximum object depth of ${this.options.maxObjectDepth}`);
126
+ }
127
+ for (const [key, value] of Object.entries(obj)) {
128
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
129
+ this.validateObjectDepth(value, `${fieldName}.${key}`, currentDepth + 1);
130
+ }
131
+ }
132
+ }
133
+ sanitizeString(value) {
134
+ if (!this.options.sanitizeStrings) {
135
+ return value;
136
+ }
137
+ let sanitized = value
138
+ .replace(/\0/g, '')
139
+ .split('')
140
+ .filter((char) => {
141
+ const code = char.charCodeAt(0);
142
+ return code > 31 && code !== 127;
143
+ })
144
+ .join('');
145
+ sanitized = sanitized.trim();
146
+ return sanitized;
147
+ }
148
+ sanitizeObject(obj) {
149
+ const sanitized = {};
150
+ for (const [key, value] of Object.entries(obj)) {
151
+ const sanitizedKey = this.sanitizeString(key);
152
+ if (typeof value === 'string') {
153
+ sanitized[sanitizedKey] = this.sanitizeString(value);
154
+ }
155
+ else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
156
+ sanitized[sanitizedKey] = this.sanitizeObject(value);
157
+ }
158
+ else if (Array.isArray(value)) {
159
+ sanitized[sanitizedKey] = value.map((item) => typeof item === 'string'
160
+ ? this.sanitizeString(item)
161
+ : typeof item === 'object' && item !== null
162
+ ? this.sanitizeObject(item)
163
+ : item);
164
+ }
165
+ else {
166
+ sanitized[sanitizedKey] = value;
167
+ }
168
+ }
169
+ return sanitized;
170
+ }
171
+ }
172
+ export const defaultValidator = new InputValidator();
173
+ export const validatePolicyConfig = (config) => defaultValidator.validatePolicyConfig(config);
174
+ export const validatePolicyRule = (rule) => defaultValidator.validatePolicyRule(rule);
175
+ export const validatePolicyContext = (context) => defaultValidator.validatePolicyContext(context);
176
+ export const validatePluginConfig = (config) => defaultValidator.validatePluginConfig(config);
@@ -0,0 +1,27 @@
1
+ export interface PolicyConfig {
2
+ id: string;
3
+ name: string;
4
+ rules: PolicyRule[];
5
+ description?: string;
6
+ version?: string;
7
+ enabled?: boolean;
8
+ }
9
+ export interface PolicyRule {
10
+ id: string;
11
+ type: string;
12
+ condition: Record<string, unknown>;
13
+ action?: string;
14
+ priority?: number;
15
+ enabled?: boolean;
16
+ }
17
+ export interface PolicyContext {
18
+ environment?: string;
19
+ metadata?: Record<string, unknown>;
20
+ [key: string]: unknown;
21
+ }
22
+ export interface PluginConfig {
23
+ name: string;
24
+ version?: string;
25
+ config?: Record<string, unknown>;
26
+ enabled?: boolean;
27
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "timonel",
3
3
  "type": "module",
4
- "version": "3.0.0",
4
+ "version": "3.1.0",
5
5
  "description": "Timonel: programmatic Helm chart generator using cdk8s (TypeScript)",
6
6
  "bin": {
7
7
  "timonel": "dist/cli.js",
@@ -59,6 +59,8 @@
59
59
  "test:integration": "vitest run --config vitest.integration.config.ts",
60
60
  "test:watch": "vitest",
61
61
  "test:coverage": "vitest run --coverage",
62
+ "doc:coverage": "tsx scripts/project-doc-coverage.ts",
63
+ "doc:coverage:validate": "tsx scripts/validate-doc-coverage.ts",
62
64
  "ci:check": "pnpm typecheck && pnpm lint && pnpm format:check && pnpm build",
63
65
  "release": "semantic-release",
64
66
  "release:dry": "semantic-release --dry-run",
@@ -100,28 +102,28 @@
100
102
  ]
101
103
  },
102
104
  "dependencies": {
103
- "cdk8s": "^2.70.28",
104
- "cdk8s-plus-33": "^2.4.6",
105
- "constructs": "^10.4.3",
105
+ "cdk8s": "^2.70.42",
106
+ "cdk8s-plus-33": "^2.4.19",
107
+ "constructs": "^10.4.4",
106
108
  "handlebars": "^4.7.8",
107
- "pino": "^10.1.0",
108
- "pino-pretty": "^13.1.2",
109
+ "pino": "^10.1.1",
110
+ "pino-pretty": "^13.1.3",
109
111
  "ts-node": "^10.9.2",
110
- "yaml": "^2.8.1"
112
+ "yaml": "^2.8.2"
111
113
  },
112
114
  "devDependencies": {
113
- "@commitlint/cli": "^20.1.0",
114
- "@commitlint/config-conventional": "^20.0.0",
115
- "@eslint/js": "^9.39.1",
115
+ "@commitlint/cli": "^20.3.1",
116
+ "@commitlint/config-conventional": "^20.3.1",
117
+ "@eslint/js": "^9.39.2",
116
118
  "@semantic-release/changelog": "^6.0.3",
117
119
  "@semantic-release/exec": "^7.1.0",
118
120
  "@semantic-release/git": "^10.0.1",
119
- "@types/node": "^24.10.1",
120
- "@typescript-eslint/eslint-plugin": "^8.47.0",
121
- "@typescript-eslint/parser": "^8.47.0",
122
- "@vitest/coverage-v8": "^3.2.4",
121
+ "@types/node": "^25.0.5",
122
+ "@typescript-eslint/eslint-plugin": "^8.52.0",
123
+ "@typescript-eslint/parser": "^8.52.0",
124
+ "@vitest/coverage-v8": "^4.0.16",
123
125
  "conventional-changelog-cli": "^5.0.0",
124
- "eslint": "^9.39.1",
126
+ "eslint": "^9.39.2",
125
127
  "eslint-config-prettier": "^10.1.8",
126
128
  "eslint-import-resolver-typescript": "^4.4.4",
127
129
  "eslint-plugin-import": "^2.32.0",
@@ -130,13 +132,13 @@
130
132
  "eslint-plugin-unused-imports": "^4.3.0",
131
133
  "husky": "^9.1.7",
132
134
  "lint-staged": "^16.2.7",
133
- "markdownlint": "^0.39.0",
134
- "markdownlint-cli": "^0.46.0",
135
- "prettier": "^3.6.2",
135
+ "markdownlint": "^0.40.0",
136
+ "markdownlint-cli": "^0.47.0",
137
+ "prettier": "^3.7.4",
136
138
  "semantic-release": "^25.0.2",
137
- "tsx": "^4.20.6",
139
+ "tsx": "^4.21.0",
138
140
  "typescript": "^5.9.3",
139
- "vitest": "^3.2.4"
141
+ "vitest": "^4.0.16"
140
142
  },
141
143
  "files": [
142
144
  "dist",