fix(appdata): Redact sensitive values in AppData logs and add redaction tests

This commit is contained in:
2025-08-16 13:15:32 +00:00
parent 2cc0da4462
commit e3a76ca577
5 changed files with 160 additions and 21 deletions

View File

@@ -16,6 +16,48 @@ function getQenv(): plugins.qenv.Qenv {
return sharedQenv;
}
// ============================================================================
// Security - Redaction for sensitive data
// ============================================================================
/**
* Redacts sensitive values in logs to prevent exposure of secrets
*/
function redactSensitiveValue(key: string, value: unknown): string {
// List of patterns that indicate sensitive data
const sensitivePatterns = [
/secret/i, /token/i, /key/i, /password/i, /pass/i,
/api/i, /credential/i, /auth/i, /private/i, /jwt/i,
/cert/i, /signature/i, /bearer/i
];
// Check if key contains sensitive pattern
const isSensitive = sensitivePatterns.some(pattern => pattern.test(key));
if (isSensitive) {
if (typeof value === 'string') {
// Show first 3 chars and length for debugging
return value.length > 3
? `${value.substring(0, 3)}...[${value.length} chars]`
: '[redacted]';
}
return '[redacted]';
}
// Check if value looks like a JWT token or base64 secret
if (typeof value === 'string') {
// JWT tokens start with eyJ
if (value.startsWith('eyJ')) {
return `eyJ...[${value.length} chars]`;
}
// Very long strings might be encoded secrets
if (value.length > 100) {
return `${value.substring(0, 50)}...[${value.length} chars total]`;
}
}
return JSON.stringify(value);
}
// ============================================================================
// Type Converters - Centralized conversion logic
// ============================================================================
@@ -210,12 +252,14 @@ function applyTransforms(value: unknown, transforms: Transform[]): unknown {
*/
async function processMappingValue(mappingString: string): Promise<unknown> {
const spec = parseMappingSpec(mappingString);
const keyName = spec.source.type === 'env' ? spec.source.key : 'hardcoded';
console.log(` 🔍 Processing mapping: "${mappingString}"`);
console.log(` Source: ${spec.source.type === 'env' ? `env:${spec.source.key}` : `hard:${spec.source.value}`}`);
console.log(` Transforms: ${spec.transforms.length > 0 ? spec.transforms.join(', ') : 'none'}`);
const rawValue = await resolveSource(spec.source);
console.log(` Raw value: ${JSON.stringify(rawValue)} (type: ${typeof rawValue})`);
console.log(` Raw value: ${redactSensitiveValue(keyName, rawValue)} (type: ${typeof rawValue})`);
if (rawValue === undefined || rawValue === null) {
console.log(` ⚠️ Raw value is undefined/null, returning undefined`);
@@ -223,7 +267,7 @@ async function processMappingValue(mappingString: string): Promise<unknown> {
}
const result = applyTransforms(rawValue, spec.transforms);
console.log(` Final value: ${JSON.stringify(result)} (type: ${typeof result})`);
console.log(` Final value: ${redactSensitiveValue(keyName, result)} (type: ${typeof result})`);
return result;
}
@@ -253,7 +297,7 @@ async function evaluateMappingValue(mappingValue: any): Promise<any> {
// Only skip if explicitly undefined
if (evaluated !== undefined) {
result[key] = evaluated;
console.log(` ✓ Nested key "${key}" = ${JSON.stringify(evaluated)} (type: ${typeof evaluated})`);
console.log(` ✓ Nested key "${key}" = ${redactSensitiveValue(key, evaluated)} (type: ${typeof evaluated})`);
} else {
console.log(` ⚠️ Nested key "${key}" evaluated to undefined, skipping`);
}
@@ -262,7 +306,8 @@ async function evaluateMappingValue(mappingValue: any): Promise<any> {
}
// For any other type (numbers, booleans, etc.), return as-is
console.log(` 📎 Returning value as-is: ${JSON.stringify(mappingValue)} (type: ${typeof mappingValue})`);
// Note: We don't have key context here, so we'll just indicate the type
console.log(` 📎 Returning value as-is: [value] (type: ${typeof mappingValue})`);
return mappingValue;
}
@@ -434,7 +479,7 @@ export class AppData<T = any> {
const valuePreview = evaluated === null ? 'null' :
typeof evaluated === 'object' ?
(Array.isArray(evaluated) ? `[${evaluated.length} items]` : `{${Object.keys(evaluated).length} keys}`) :
JSON.stringify(evaluated);
redactSensitiveValue(key, evaluated);
console.log(` ✅ Successfully processed key "${key}" = ${valuePreview} (type: ${valueType})`);
} else {
console.log(` ⚠️ Key "${key}" evaluated to undefined, skipping`);