timonel 3.0.0-beta.1 → 3.0.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.
@@ -1,4 +1,5 @@
1
1
  import { Document, Scalar, isMap, isScalar, visit, Pair } from 'yaml';
2
+ import { SecurityUtils } from '../security.js';
2
3
  import { isHelmConstruct, isHelmExpression, createHelmExpression, } from './helmControlStructures.js';
3
4
  import { isHelmValue, isHelmFieldConditional, isHelmRange, isHelmWith, } from './valuesRef.js';
4
5
  const FIELD_CONDITIONAL = '__FIELD_CONDITIONAL__:';
@@ -542,7 +543,8 @@ export function dumpHelmAwareYaml(obj, options = {}) {
542
543
  const contentLines = content.split('\n');
543
544
  const firstContentLine = contentLines[0]?.trim() || '';
544
545
  if (firstContentLine.startsWith(`${fieldKey}:`)) {
545
- contentLines[0] = contentLines[0].replace(new RegExp(`^\\s*${fieldKey}:\\s*`), '');
546
+ const escapedFieldKey = fieldKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
547
+ contentLines[0] = contentLines[0].replace(new RegExp(`^\\s*${escapedFieldKey}:\\s*`), '');
546
548
  content = contentLines.join('\n');
547
549
  }
548
550
  const contentFormatted = content
@@ -640,7 +642,7 @@ export function postProcessFieldConditionals(yaml) {
640
642
  .join('\n');
641
643
  result = result.substring(0, secondLastNewline + 1) + processed + result.substring(markerEnd);
642
644
  }
643
- result = result.replace(/:\s*\|[-+]?\s*\n(\s*\{\{)/g, ':\n$1');
645
+ result = result.replace(/:\s*\|[-+]?\s*\n([ \t]*\{\{)/g, ':\n$1');
644
646
  result = result.replace(/"(\{\{[\s\S]*?\}\})"/g, '$1');
645
647
  return result;
646
648
  }
@@ -718,13 +720,14 @@ export function parseHelmExpressions(content) {
718
720
  let match;
719
721
  regex.lastIndex = 0;
720
722
  while ((match = regex.exec(line)) !== null) {
723
+ const sanitizedExpression = SecurityUtils.sanitizeLogMessage(match[0] || '');
721
724
  expressions.push({
722
725
  type,
723
- expression: match[0],
726
+ expression: sanitizedExpression,
724
727
  startLine: i + 1,
725
728
  startCol: match.index + 1,
726
729
  endLine: i + 1,
727
- endCol: match.index + match[0].length + 1,
730
+ endCol: match.index + (match[0]?.length || 0) + 1,
728
731
  });
729
732
  if (match.index === regex.lastIndex)
730
733
  regex.lastIndex++;
@@ -780,14 +783,19 @@ function validateFunctionCalls(_content) {
780
783
  function checkCommonIssues(yaml, warnings) {
781
784
  const deprecatedFunctions = ['template'];
782
785
  for (const func of deprecatedFunctions) {
783
- const pattern = new RegExp(`\\{\\{[^}]*\\b${func}\\b[^}]*\\}\\}`, 'g');
786
+ if (!/^[a-zA-Z0-9_]+$/.test(func)) {
787
+ continue;
788
+ }
789
+ const escapedFunc = func.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
790
+ const pattern = new RegExp(`\\{\\{[^}]*\\b${escapedFunc}\\b[^}]*\\}\\}`, 'g');
784
791
  let match;
785
792
  while ((match = pattern.exec(yaml)) !== null) {
793
+ const sanitizedFunc = SecurityUtils.sanitizeLogMessage(func);
786
794
  warnings.push({
787
795
  type: 'semantic',
788
- message: `Function '${func}' is deprecated`,
796
+ message: `Function '${sanitizedFunc}' is deprecated`,
789
797
  expression: match[0],
790
- suggestion: `Consider avoiding deprecated function '${func}'`,
798
+ suggestion: `Consider avoiding deprecated function '${sanitizedFunc}'`,
791
799
  });
792
800
  }
793
801
  }
@@ -798,11 +806,13 @@ function checkQuotedExpressions(yaml, warnings) {
798
806
  let match;
799
807
  pattern.lastIndex = 0;
800
808
  while ((match = pattern.exec(yaml)) !== null) {
809
+ const sanitizedMatch = SecurityUtils.sanitizeLogMessage(match[1] || '');
810
+ const sanitizedExpression = SecurityUtils.sanitizeLogMessage(match[0] || '');
801
811
  warnings.push({
802
812
  type: 'semantic',
803
813
  message: 'Helm expression should not be quoted',
804
- expression: match[0],
805
- suggestion: `Remove quotes around: ${match[1]}`,
814
+ expression: sanitizedExpression,
815
+ suggestion: `Remove quotes around: ${sanitizedMatch}`,
806
816
  });
807
817
  }
808
818
  }
@@ -180,19 +180,24 @@ export class TimonelLogger {
180
180
  }
181
181
  }
182
182
  requestSerializer(req) {
183
- if (!req || typeof req !== 'object')
183
+ try {
184
+ if (!req || typeof req !== 'object')
185
+ return {};
186
+ const request = req;
187
+ return {
188
+ method: request.method,
189
+ url: SecurityUtils.sanitizeLogMessage(request.url || ''),
190
+ headers: {
191
+ 'user-agent': request.headers?.['user-agent'],
192
+ 'content-type': request.headers?.['content-type'],
193
+ },
194
+ remoteAddress: request.remoteAddress,
195
+ remotePort: request.remotePort,
196
+ };
197
+ }
198
+ catch {
184
199
  return {};
185
- const request = req;
186
- return {
187
- method: request.method,
188
- url: SecurityUtils.sanitizeLogMessage(request.url || ''),
189
- headers: {
190
- 'user-agent': request.headers?.['user-agent'],
191
- 'content-type': request.headers?.['content-type'],
192
- },
193
- remoteAddress: request.remoteAddress,
194
- remotePort: request.remotePort,
195
- };
200
+ }
196
201
  }
197
202
  responseSerializer(res) {
198
203
  if (!res || typeof res !== 'object')
@@ -256,40 +261,50 @@ export class TimonelLogger {
256
261
  }
257
262
  }
258
263
  sanitizeContext(context) {
259
- const sanitized = {};
260
- for (const [key, value] of Object.entries(context)) {
261
- if (TimonelLogger.SENSITIVE_FIELDS.has(key.toLowerCase())) {
262
- sanitized[key] = '[REDACTED]';
263
- }
264
- else if (typeof value === 'string') {
265
- sanitized[key] = SecurityUtils.sanitizeLogMessage(value);
266
- }
267
- else if (value && typeof value === 'object') {
268
- sanitized[key] = this.sanitizeNestedObject(value, 2);
269
- }
270
- else {
271
- sanitized[key] = value;
264
+ try {
265
+ const sanitized = {};
266
+ for (const [key, value] of Object.entries(context)) {
267
+ if (TimonelLogger.SENSITIVE_FIELDS.has(key.toLowerCase())) {
268
+ sanitized[key] = '[REDACTED]';
269
+ }
270
+ else if (typeof value === 'string') {
271
+ sanitized[key] = SecurityUtils.sanitizeLogMessage(value);
272
+ }
273
+ else if (value && typeof value === 'object') {
274
+ sanitized[key] = this.sanitizeNestedObject(value, 2);
275
+ }
276
+ else {
277
+ sanitized[key] = value;
278
+ }
272
279
  }
280
+ return sanitized;
281
+ }
282
+ catch {
283
+ return {};
273
284
  }
274
- return sanitized;
275
285
  }
276
286
  sanitizeNestedObject(obj, depth) {
277
- if (depth <= 0 || !obj || typeof obj !== 'object') {
278
- return obj;
279
- }
280
- const sanitized = {};
281
- for (const [key, value] of Object.entries(obj)) {
282
- if (typeof value === 'string') {
283
- sanitized[key] = SecurityUtils.sanitizeLogMessage(value);
287
+ try {
288
+ if (depth <= 0 || !obj || typeof obj !== 'object') {
289
+ return obj;
284
290
  }
285
- else if (value && typeof value === 'object') {
286
- sanitized[key] = this.sanitizeNestedObject(value, depth - 1);
287
- }
288
- else {
289
- sanitized[key] = value;
291
+ const sanitized = {};
292
+ for (const [key, value] of Object.entries(obj)) {
293
+ if (typeof value === 'string') {
294
+ sanitized[key] = SecurityUtils.sanitizeLogMessage(value);
295
+ }
296
+ else if (value && typeof value === 'object') {
297
+ sanitized[key] = this.sanitizeNestedObject(value, depth - 1);
298
+ }
299
+ else {
300
+ sanitized[key] = value;
301
+ }
290
302
  }
303
+ return sanitized;
304
+ }
305
+ catch {
306
+ return obj;
291
307
  }
292
- return sanitized;
293
308
  }
294
309
  child(context) {
295
310
  const sanitizedContext = this.sanitizeContext(context);
@@ -10,6 +10,9 @@ function serializeValue(value) {
10
10
  return String(value);
11
11
  }
12
12
  function createCondition(condition) {
13
+ if (!condition || typeof condition !== 'string') {
14
+ throw new Error('Condition must be a non-empty string');
15
+ }
13
16
  return {
14
17
  [HELM_VALUE_SYMBOL]: true,
15
18
  __condition: condition,
@@ -17,9 +20,15 @@ function createCondition(condition) {
17
20
  return createCondition(`not (${this.__condition})`);
18
21
  },
19
22
  and(other) {
23
+ if (!other || typeof other.__condition !== 'string') {
24
+ throw new Error('Invalid HelmCondition provided to and()');
25
+ }
20
26
  return createCondition(`and (${this.__condition}) (${other.__condition})`);
21
27
  },
22
28
  or(other) {
29
+ if (!other || typeof other.__condition !== 'string') {
30
+ throw new Error('Invalid HelmCondition provided to or()');
31
+ }
23
32
  return createCondition(`or (${this.__condition}) (${other.__condition})`);
24
33
  },
25
34
  toString() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "timonel",
3
3
  "type": "module",
4
- "version": "3.0.0-beta.1",
4
+ "version": "3.0.0",
5
5
  "description": "Timonel: programmatic Helm chart generator using cdk8s (TypeScript)",
6
6
  "bin": {
7
7
  "timonel": "dist/cli.js",