From f8f2908f64080d9510d2473690934f75052d0fb7 Mon Sep 17 00:00:00 2001 From: Tofik Hasanov Date: Fri, 5 Jun 2026 18:19:20 -0400 Subject: [PATCH] fix(cloud-security): allow an empty filterPattern for PutMetricFilter validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cubic review on #3050: PutMetricFilterCommand had `filterPattern` in REQUIRED_PARAMS, which (via hasRequiredParamValue) rejects an empty string — but AWS allows an empty filterPattern (min length 0; an empty pattern matches all log events). The parameter must be PRESENT, but its value may be "". - Move `filterPattern` out of REQUIRED_PARAMS (which also rejects "") into a new REQUIRED_PRESENT_PARAMS check that only rejects a missing/null value, so "" is accepted while a truly-omitted pattern is still caught before execution. - logGroupName / filterName / metricTransformations stay non-empty-required. - Tests: empty filterPattern is accepted; a missing one is reported via the present-check. Low practical impact (our CIS metric-filter remediations always emit a non-empty pattern), but aligns the validator with AWS semantics. Executor spec green (43); typecheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../aws-command-executor.spec.ts | 22 +++++++++++++- .../cloud-security/aws-command-executor.ts | 30 +++++++++++++++---- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/apps/api/src/cloud-security/aws-command-executor.spec.ts b/apps/api/src/cloud-security/aws-command-executor.spec.ts index 4b875fa53..ff998eac4 100644 --- a/apps/api/src/cloud-security/aws-command-executor.spec.ts +++ b/apps/api/src/cloud-security/aws-command-executor.spec.ts @@ -422,12 +422,32 @@ describe('PutMetricFilterCommand required params + normalization', () => { ]); expect(errors).toEqual( expect.arrayContaining([ - expect.stringMatching(/Required param "filterPattern" is missing/), + // filterPattern must be PRESENT (but may be empty), so a missing one is + // reported via the present-check, not the non-empty REQUIRED_PARAMS one. + expect.stringMatching(/Required param "filterPattern" must be provided/), expect.stringMatching(/Required param "metricTransformations" is missing/), ]), ); }); + it('allows an empty filterPattern (AWS accepts "" — it matches all events)', () => { + const errors = validatePlanSteps([ + step({ + service: 'cloudwatch-logs', + command: 'PutMetricFilterCommand', + params: { + logGroupName: 'lg', + filterName: 'fn', + filterPattern: '', + metricTransformations: [ + { metricName: 'm', metricNamespace: 'CloudTrailMetrics', metricValue: '1' }, + ], + }, + }), + ]); + expect(errors.filter((e) => /filterPattern/.test(e))).toHaveLength(0); + }); + it('does not error when all PutMetricFilter params are present', () => { const errors = validatePlanSteps([ step({ diff --git a/apps/api/src/cloud-security/aws-command-executor.ts b/apps/api/src/cloud-security/aws-command-executor.ts index ec786902c..713ea3e21 100644 --- a/apps/api/src/cloud-security/aws-command-executor.ts +++ b/apps/api/src/cloud-security/aws-command-executor.ts @@ -165,12 +165,18 @@ export const REQUIRED_PARAMS: Record = { StartConfigurationRecorderCommand: ['ConfigurationRecorderName'], PutBucketPolicyCommand: ['Bucket', 'Policy'], CreateTrailCommand: ['Name', 'S3BucketName'], - PutMetricFilterCommand: [ - 'logGroupName', - 'filterName', - 'filterPattern', - 'metricTransformations', - ], + PutMetricFilterCommand: ['logGroupName', 'filterName', 'metricTransformations'], +}; + +/** + * Params that must be PRESENT in the request but may legitimately be an empty + * string — unlike REQUIRED_PARAMS, which also rejects "". For example, AWS + * CloudWatch Logs PutMetricFilter requires `filterPattern` to be supplied but + * accepts an empty pattern (an empty filterPattern matches all log events), so + * we must reject only a missing/null value, not "". + */ +const REQUIRED_PRESENT_PARAMS: Record = { + PutMetricFilterCommand: ['filterPattern'], }; const REQUIRED_PARAM_ONE_OF: Record = { @@ -649,6 +655,18 @@ export function validatePlanSteps(steps: AwsCommandStep[]): string[] { } } + const requiredPresent = REQUIRED_PRESENT_PARAMS[step.command]; + if (requiredPresent) { + for (const key of requiredPresent) { + const value = step.params?.[key]; + // Must be supplied, but an empty string is valid (e.g. an empty + // CloudWatch filterPattern matches all log events). + if (value === undefined || value === null) { + errors.push(`${prefix}: Required param "${key}" must be provided`); + } + } + } + const oneOfGroups = REQUIRED_PARAM_ONE_OF[step.command]; if (oneOfGroups) { for (const group of oneOfGroups) {