@jaypie/constructs 1.2.58 → 1.2.60

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/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as cdk from 'aws-cdk-lib';
2
- import { Tags, Stack, Fn, CfnOutput, SecretValue, RemovalPolicy, Duration, CfnStack } from 'aws-cdk-lib';
2
+ import { Tags, Stack, SecretValue, RemovalPolicy, Fn, CfnOutput, Duration, CfnStack } from 'aws-cdk-lib';
3
3
  import * as s3 from 'aws-cdk-lib/aws-s3';
4
4
  import { Bucket, StorageClass, BucketAccessControl, EventType } from 'aws-cdk-lib/aws-s3';
5
5
  import { Construct } from 'constructs';
@@ -937,115 +937,67 @@ const resolveParamsAndSecrets = ({ paramsAndSecrets, options, } = {}) => {
937
937
  return resolvedParamsAndSecrets;
938
938
  };
939
939
 
940
- // It is a consumer if the environment is ephemeral
941
- function checkEnvIsConsumer$1(env = process.env) {
942
- return (env.PROJECT_ENV === CDK$2.ENV.PERSONAL ||
943
- !!env.CDK_ENV_PERSONAL ||
944
- /** @deprecated */ env.PROJECT_ENV === "ephemeral" ||
945
- /** @deprecated */ !!env.CDK_ENV_EPHEMERAL);
946
- }
947
- function checkEnvIsProvider$1(env = process.env) {
948
- return env.PROJECT_ENV === CDK$2.ENV.SANDBOX;
949
- }
950
- function cleanName$1(name) {
951
- return name.replace(/[^a-zA-Z0-9:-]/g, "");
952
- }
953
- function exportEnvName$1(name, env = process.env, consumer = false) {
954
- let rawName;
955
- if (checkEnvIsProvider$1(env)) {
956
- rawName = `env-${env.PROJECT_ENV}-${env.PROJECT_KEY}-${name}`;
957
- // Clean the entire name to only allow alphanumeric, colons, and hyphens
958
- return cleanName$1(rawName);
959
- }
960
- else {
961
- if (consumer || checkEnvIsConsumer$1(env)) {
962
- rawName = `env-${CDK$2.ENV.SANDBOX}-${env.PROJECT_KEY}-${name}`;
963
- }
964
- else {
965
- rawName = `env-${env.PROJECT_ENV}-${env.PROJECT_KEY}-${name}`;
966
- }
967
- }
968
- return cleanName$1(rawName);
969
- }
970
- class JaypieEnvSecret extends Construct {
940
+ class JaypieSecret extends Construct {
971
941
  constructor(scope, idOrEnvKey, props) {
972
942
  // Shorthand detection: treat idOrEnvKey as envKey when envKey prop is
973
943
  // not set and idOrEnvKey either looks like a SCREAMING_SNAKE_CASE env
974
944
  // var name or is already present in process.env. Convention-based
975
945
  // detection ensures missing env vars still go through envKey validation
976
946
  // instead of silently creating an empty secret.
947
+ const prefix = new.target.shorthandPrefix;
977
948
  const looksLikeEnvKey = /^[A-Z][A-Z0-9_]*$/.test(idOrEnvKey);
978
949
  const treatAsEnvKey = (!props || props.envKey === undefined) &&
979
950
  (looksLikeEnvKey ||
980
951
  (typeof process.env[idOrEnvKey] === "string" &&
981
952
  process.env[idOrEnvKey] !== ""));
982
- const id = treatAsEnvKey ? `EnvSecret_${idOrEnvKey}` : idOrEnvKey;
953
+ const id = treatAsEnvKey ? `${prefix}${idOrEnvKey}` : idOrEnvKey;
983
954
  super(scope, id);
984
- const { consumer = checkEnvIsConsumer$1(), envKey: envKeyProp, export: exportParam, generateSecretString, provider = checkEnvIsProvider$1(), removalPolicy, roleTag, vendorTag, value, } = props || {};
985
- const envKey = treatAsEnvKey ? idOrEnvKey : envKeyProp;
955
+ const envKey = treatAsEnvKey ? idOrEnvKey : props?.envKey;
986
956
  this._envKey = envKey;
987
- let exportName;
988
- if (!exportParam) {
989
- // When shorthand detection is active, use the full construct id (which
990
- // includes the "EnvSecret_" prefix) so the export name matches what was
991
- // produced by earlier versions of this construct. Using the raw envKey
992
- // here produces a shorter name that breaks existing cross-stack imports.
993
- const exportSource = treatAsEnvKey ? id : envKey || id;
994
- exportName = exportEnvName$1(exportSource, process.env, consumer);
995
- }
996
- else {
997
- exportName = cleanName$1(exportParam);
998
- }
999
- if (!consumer &&
1000
- envKey &&
957
+ this._secret = this.buildSecret({
958
+ envKey,
959
+ id,
960
+ props: props || {},
961
+ treatAsEnvKey,
962
+ });
963
+ }
964
+ /**
965
+ * Builds the underlying secret. The base implementation always creates a new
966
+ * Secrets Manager secret from an envKey value, an explicit value, or a
967
+ * generated string. Subclasses may override to import an existing secret or
968
+ * emit cross-stack outputs.
969
+ */
970
+ buildSecret(context) {
971
+ const { envKey, id, props } = context;
972
+ const { generateSecretString, removalPolicy, roleTag, vendorTag, value } = props;
973
+ if (envKey &&
1001
974
  !process.env[envKey] &&
1002
975
  value === undefined &&
1003
976
  !generateSecretString) {
1004
- throw new ConfigurationError(`JaypieEnvSecret(${id}): envKey "${envKey}" is empty in process.env and no value or generateSecretString was provided`);
977
+ throw new ConfigurationError(`JaypieSecret(${id}): envKey "${envKey}" is empty in process.env and no value or generateSecretString was provided`);
1005
978
  }
1006
- if (consumer) {
1007
- const secretName = Fn.importValue(exportName);
1008
- this._secret = secretsmanager.Secret.fromSecretNameV2(this, id, secretName);
1009
- // Add CfnOutput for consumer secrets
1010
- new CfnOutput(this, `ConsumedName`, {
1011
- value: this._secret.secretName,
1012
- });
979
+ const secretValue = envKey && process.env[envKey] ? process.env[envKey] : value;
980
+ const secret = new secretsmanager.Secret(this, id, {
981
+ generateSecretString,
982
+ secretStringValue: !generateSecretString && secretValue
983
+ ? SecretValue.unsafePlainText(secretValue)
984
+ : undefined,
985
+ });
986
+ if (removalPolicy !== undefined) {
987
+ const policy = typeof removalPolicy === "boolean"
988
+ ? removalPolicy
989
+ ? RemovalPolicy.RETAIN
990
+ : RemovalPolicy.DESTROY
991
+ : removalPolicy;
992
+ secret.applyRemovalPolicy(policy);
1013
993
  }
1014
- else {
1015
- const secretValue = envKey && process.env[envKey] ? process.env[envKey] : value;
1016
- const secretProps = {
1017
- generateSecretString,
1018
- secretStringValue: !generateSecretString && secretValue
1019
- ? SecretValue.unsafePlainText(secretValue)
1020
- : undefined,
1021
- };
1022
- this._secret = new secretsmanager.Secret(this, id, secretProps);
1023
- if (removalPolicy !== undefined) {
1024
- const policy = typeof removalPolicy === "boolean"
1025
- ? removalPolicy
1026
- ? RemovalPolicy.RETAIN
1027
- : RemovalPolicy.DESTROY
1028
- : removalPolicy;
1029
- this._secret.applyRemovalPolicy(policy);
1030
- }
1031
- if (roleTag) {
1032
- Tags.of(this._secret).add(CDK$2.TAG.ROLE, roleTag);
1033
- }
1034
- if (vendorTag) {
1035
- Tags.of(this._secret).add(CDK$2.TAG.VENDOR, vendorTag);
1036
- }
1037
- if (provider) {
1038
- new CfnOutput(this, `ProvidedName`, {
1039
- value: this._secret.secretName,
1040
- exportName,
1041
- });
1042
- }
1043
- else {
1044
- new CfnOutput(this, `CreatedName`, {
1045
- value: this._secret.secretName,
1046
- });
1047
- }
994
+ if (roleTag) {
995
+ Tags.of(secret).add(CDK$2.TAG.ROLE, roleTag);
996
+ }
997
+ if (vendorTag) {
998
+ Tags.of(secret).add(CDK$2.TAG.VENDOR, vendorTag);
1048
999
  }
1000
+ return secret;
1049
1001
  }
1050
1002
  // IResource implementation
1051
1003
  get stack() {
@@ -1107,6 +1059,117 @@ class JaypieEnvSecret extends Construct {
1107
1059
  return this._envKey;
1108
1060
  }
1109
1061
  }
1062
+ // Construct id prefix used when an envKey is detected via shorthand.
1063
+ // Subclasses override this to preserve their own naming conventions.
1064
+ JaypieSecret.shorthandPrefix = "Secret_";
1065
+
1066
+ // It is a consumer if the environment is ephemeral
1067
+ function checkEnvIsConsumer$1(env = process.env) {
1068
+ return (env.PROJECT_ENV === CDK$2.ENV.PERSONAL ||
1069
+ !!env.CDK_ENV_PERSONAL ||
1070
+ /** @deprecated */ env.PROJECT_ENV === "ephemeral" ||
1071
+ /** @deprecated */ !!env.CDK_ENV_EPHEMERAL);
1072
+ }
1073
+ function checkEnvIsProvider$1(env = process.env) {
1074
+ return env.PROJECT_ENV === CDK$2.ENV.SANDBOX;
1075
+ }
1076
+ function cleanName$1(name) {
1077
+ return name.replace(/[^a-zA-Z0-9:-]/g, "");
1078
+ }
1079
+ function exportEnvName$1(name, env = process.env, consumer = false) {
1080
+ let rawName;
1081
+ if (checkEnvIsProvider$1(env)) {
1082
+ rawName = `env-${env.PROJECT_ENV}-${env.PROJECT_KEY}-${name}`;
1083
+ // Clean the entire name to only allow alphanumeric, colons, and hyphens
1084
+ return cleanName$1(rawName);
1085
+ }
1086
+ else {
1087
+ if (consumer || checkEnvIsConsumer$1(env)) {
1088
+ rawName = `env-${CDK$2.ENV.SANDBOX}-${env.PROJECT_KEY}-${name}`;
1089
+ }
1090
+ else {
1091
+ rawName = `env-${env.PROJECT_ENV}-${env.PROJECT_KEY}-${name}`;
1092
+ }
1093
+ }
1094
+ return cleanName$1(rawName);
1095
+ }
1096
+ /**
1097
+ * @deprecated Use {@link JaypieSecret}. JaypieEnvSecret layers an
1098
+ * environment-driven provider/consumer cross-stack pattern on top of
1099
+ * JaypieSecret and will be removed in 2.0.
1100
+ */
1101
+ class JaypieEnvSecret extends JaypieSecret {
1102
+ constructor(scope, idOrEnvKey, props) {
1103
+ super(scope, idOrEnvKey, props);
1104
+ }
1105
+ buildSecret(context) {
1106
+ const { envKey, id, treatAsEnvKey } = context;
1107
+ const props = context.props;
1108
+ const { consumer = checkEnvIsConsumer$1(), export: exportParam, generateSecretString, provider = checkEnvIsProvider$1(), removalPolicy, roleTag, vendorTag, value, } = props;
1109
+ let exportName;
1110
+ if (!exportParam) {
1111
+ // When shorthand detection is active, use the full construct id (which
1112
+ // includes the "EnvSecret_" prefix) so the export name matches what was
1113
+ // produced by earlier versions of this construct. Using the raw envKey
1114
+ // here produces a shorter name that breaks existing cross-stack imports.
1115
+ const exportSource = treatAsEnvKey ? id : envKey || id;
1116
+ exportName = exportEnvName$1(exportSource, process.env, consumer);
1117
+ }
1118
+ else {
1119
+ exportName = cleanName$1(exportParam);
1120
+ }
1121
+ if (!consumer &&
1122
+ envKey &&
1123
+ !process.env[envKey] &&
1124
+ value === undefined &&
1125
+ !generateSecretString) {
1126
+ throw new ConfigurationError(`JaypieEnvSecret(${id}): envKey "${envKey}" is empty in process.env and no value or generateSecretString was provided`);
1127
+ }
1128
+ if (consumer) {
1129
+ const secretName = Fn.importValue(exportName);
1130
+ const secret = secretsmanager.Secret.fromSecretNameV2(this, id, secretName);
1131
+ // Add CfnOutput for consumer secrets
1132
+ new CfnOutput(this, `ConsumedName`, {
1133
+ value: secret.secretName,
1134
+ });
1135
+ return secret;
1136
+ }
1137
+ const secretValue = envKey && process.env[envKey] ? process.env[envKey] : value;
1138
+ const secret = new secretsmanager.Secret(this, id, {
1139
+ generateSecretString,
1140
+ secretStringValue: !generateSecretString && secretValue
1141
+ ? SecretValue.unsafePlainText(secretValue)
1142
+ : undefined,
1143
+ });
1144
+ if (removalPolicy !== undefined) {
1145
+ const policy = typeof removalPolicy === "boolean"
1146
+ ? removalPolicy
1147
+ ? RemovalPolicy.RETAIN
1148
+ : RemovalPolicy.DESTROY
1149
+ : removalPolicy;
1150
+ secret.applyRemovalPolicy(policy);
1151
+ }
1152
+ if (roleTag) {
1153
+ Tags.of(secret).add(CDK$2.TAG.ROLE, roleTag);
1154
+ }
1155
+ if (vendorTag) {
1156
+ Tags.of(secret).add(CDK$2.TAG.VENDOR, vendorTag);
1157
+ }
1158
+ if (provider) {
1159
+ new CfnOutput(this, `ProvidedName`, {
1160
+ value: secret.secretName,
1161
+ exportName,
1162
+ });
1163
+ }
1164
+ else {
1165
+ new CfnOutput(this, `CreatedName`, {
1166
+ value: secret.secretName,
1167
+ });
1168
+ }
1169
+ return secret;
1170
+ }
1171
+ }
1172
+ JaypieEnvSecret.shorthandPrefix = "EnvSecret_";
1110
1173
 
1111
1174
  /**
1112
1175
  * Cache for secrets by scope to avoid creating duplicates.
@@ -1143,11 +1206,11 @@ function getOrCreateSecret(scope, envKey, props) {
1143
1206
  return secret;
1144
1207
  }
1145
1208
  /**
1146
- * Resolves secrets input to an array of JaypieEnvSecret instances.
1209
+ * Resolves secrets input to an array of JaypieSecret instances.
1147
1210
  *
1148
- * When an item is already a JaypieEnvSecret, it's passed through as-is.
1149
- * When an item is a string, a JaypieEnvSecret is created (or reused from cache)
1150
- * with the string as the envKey.
1211
+ * When an item is already a JaypieSecret (including a JaypieEnvSecret), it's
1212
+ * passed through as-is. When an item is a string, a JaypieEnvSecret is created
1213
+ * (or reused from cache) with the string as the envKey.
1151
1214
  *
1152
1215
  * Secrets are cached per scope to avoid creating duplicate secrets when
1153
1216
  * multiple constructs in the same scope reference the same secret.
@@ -1183,7 +1246,7 @@ function resolveSecrets(scope, secrets) {
1183
1246
  if (typeof item === "string") {
1184
1247
  return getOrCreateSecret(scope, item);
1185
1248
  }
1186
- // Already a JaypieEnvSecret instance
1249
+ // Already a JaypieSecret (or JaypieEnvSecret) instance
1187
1250
  return item;
1188
1251
  });
1189
1252
  }
@@ -1204,6 +1267,148 @@ function clearAllSecretsCaches() {
1204
1267
  // between test runs. For testing, use clearSecretsCache(scope) instead.
1205
1268
  }
1206
1269
 
1270
+ /**
1271
+ * Canonical sub-rule names for each AWS managed rule group, as published in the
1272
+ * AWS WAF developer guide. Used to validate `waf.allow` and
1273
+ * `waf.managedRuleOverrides` rule names at synth time — AWS WAF matches
1274
+ * `RuleActionOverride` on the exact rule *name* and silently ignores names that
1275
+ * match no rule, so a typo or a label/name casing mismatch (e.g. the label
1276
+ * `…:NoUserAgent_Header` vs the rule name `NoUserAgent_HEADER`) becomes an
1277
+ * undiagnosable no-op.
1278
+ *
1279
+ * Groups absent from this map (custom rule groups, or AWS groups not yet
1280
+ * mirrored here) are not validated.
1281
+ *
1282
+ * @see https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html
1283
+ */
1284
+ const AWS_MANAGED_RULE_GROUPS = {
1285
+ AWSManagedRulesAdminProtectionRuleSet: ["AdminProtection_URIPATH"],
1286
+ AWSManagedRulesAmazonIpReputationList: [
1287
+ "AWSManagedIPDDoSList",
1288
+ "AWSManagedIPReputationList",
1289
+ "AWSManagedReconnaissanceList",
1290
+ ],
1291
+ AWSManagedRulesAnonymousIpList: ["AnonymousIPList", "HostingProviderIPList"],
1292
+ AWSManagedRulesCommonRuleSet: [
1293
+ "CrossSiteScripting_BODY",
1294
+ "CrossSiteScripting_COOKIE",
1295
+ "CrossSiteScripting_QUERYARGUMENTS",
1296
+ "CrossSiteScripting_URIPATH",
1297
+ "EC2MetaDataSSRF_BODY",
1298
+ "EC2MetaDataSSRF_COOKIE",
1299
+ "EC2MetaDataSSRF_QUERYARGUMENTS",
1300
+ "EC2MetaDataSSRF_URIPATH",
1301
+ "GenericLFI_BODY",
1302
+ "GenericLFI_QUERYARGUMENTS",
1303
+ "GenericLFI_URIPATH",
1304
+ "GenericRFI_BODY",
1305
+ "GenericRFI_QUERYARGUMENTS",
1306
+ "GenericRFI_URIPATH",
1307
+ "NoUserAgent_HEADER",
1308
+ "RestrictedExtensions_QUERYARGUMENTS",
1309
+ "RestrictedExtensions_URIPATH",
1310
+ "SizeRestrictions_BODY",
1311
+ "SizeRestrictions_Cookie_HEADER",
1312
+ "SizeRestrictions_QUERYSTRING",
1313
+ "SizeRestrictions_URIPATH",
1314
+ "UserAgent_BadBots_HEADER",
1315
+ ],
1316
+ AWSManagedRulesKnownBadInputsRuleSet: [
1317
+ "ExploitablePaths_URIPATH",
1318
+ "Host_localhost_HEADER",
1319
+ "JavaDeserializationRCE_BODY",
1320
+ "JavaDeserializationRCE_HEADER",
1321
+ "JavaDeserializationRCE_QUERYSTRING",
1322
+ "JavaDeserializationRCE_URIPATH",
1323
+ "Log4JRCE_BODY",
1324
+ "Log4JRCE_HEADER",
1325
+ "Log4JRCE_QUERYSTRING",
1326
+ "Log4JRCE_URIPATH",
1327
+ "PROPFIND_METHOD",
1328
+ "ReactJSRCE_BODY",
1329
+ ],
1330
+ AWSManagedRulesLinuxRuleSet: ["LFI_HEADER", "LFI_QUERYSTRING", "LFI_URIPATH"],
1331
+ AWSManagedRulesPHPRuleSet: [
1332
+ "PHPHighRiskMethodsVariables_BODY",
1333
+ "PHPHighRiskMethodsVariables_HEADER",
1334
+ "PHPHighRiskMethodsVariables_QUERYSTRING",
1335
+ "PHPHighRiskMethodsVariables_URIPATH",
1336
+ ],
1337
+ AWSManagedRulesSQLiRuleSet: [
1338
+ "SQLiExtendedPatterns_BODY",
1339
+ "SQLiExtendedPatterns_HEADER",
1340
+ "SQLiExtendedPatterns_QUERYARGUMENTS",
1341
+ "SQLiExtendedPatterns_URIPATH",
1342
+ "SQLi_BODY",
1343
+ "SQLi_COOKIE",
1344
+ "SQLi_QUERYARGUMENTS",
1345
+ "SQLi_URIPATH",
1346
+ ],
1347
+ AWSManagedRulesUnixRuleSet: [
1348
+ "UNIXShellCommandsVariables_BODY",
1349
+ "UNIXShellCommandsVariables_HEADER",
1350
+ "UNIXShellCommandsVariables_QUERYSTRING",
1351
+ ],
1352
+ AWSManagedRulesWindowsRuleSet: [
1353
+ "PowerShellCommands_BODY",
1354
+ "PowerShellCommands_COOKIE",
1355
+ "PowerShellCommands_QUERYARGUMENTS",
1356
+ "WindowsShellCommands_BODY",
1357
+ "WindowsShellCommands_HEADER",
1358
+ "WindowsShellCommands_QUERYARGUMENTS",
1359
+ "WindowsShellCommands_QUERYSTRING",
1360
+ "WindowsShellCommands_URIPATH",
1361
+ ],
1362
+ AWSManagedRulesWordPressRuleSet: [
1363
+ "WordPressExploitableCommands_QUERYSTRING",
1364
+ "WordPressExploitablePaths_URIPATH",
1365
+ ],
1366
+ };
1367
+ /**
1368
+ * Throw a ConfigurationError if any `waf.allow` or `waf.managedRuleOverrides`
1369
+ * rule name does not exist in its AWS managed rule group. Groups not present in
1370
+ * AWS_MANAGED_RULE_GROUPS (custom groups) are skipped. A name that matches no
1371
+ * rule would otherwise be silently ignored by AWS WAF.
1372
+ */
1373
+ function assertValidWafRuleNames({ allow, managedRuleOverrides, } = {}) {
1374
+ // Collect (group → referenced rule name) pairs from both inputs.
1375
+ const references = [];
1376
+ if (managedRuleOverrides) {
1377
+ for (const [group, overrides] of Object.entries(managedRuleOverrides)) {
1378
+ for (const override of overrides ?? []) {
1379
+ if (override?.name)
1380
+ references.push({ group, ruleName: override.name });
1381
+ }
1382
+ }
1383
+ }
1384
+ const allowEntries = allow ? (Array.isArray(allow) ? allow : [allow]) : [];
1385
+ for (const entry of allowEntries) {
1386
+ for (const key of Object.keys(entry)) {
1387
+ if (key === "path")
1388
+ continue;
1389
+ const raw = entry[key];
1390
+ if (raw == null)
1391
+ continue;
1392
+ const ruleNames = Array.isArray(raw) ? raw : [raw];
1393
+ for (const ruleName of ruleNames) {
1394
+ references.push({ group: key, ruleName });
1395
+ }
1396
+ }
1397
+ }
1398
+ for (const { group, ruleName } of references) {
1399
+ const validNames = AWS_MANAGED_RULE_GROUPS[group];
1400
+ if (!validNames)
1401
+ continue; // Unknown/custom group — cannot validate
1402
+ if (!validNames.includes(ruleName)) {
1403
+ throw new ConfigurationError(`WAF rule "${ruleName}" is not a rule in ${group}. AWS WAF matches ` +
1404
+ `RuleActionOverrides on the exact rule name and silently ignores ` +
1405
+ `unmatched names (note the label/name casing trap, e.g. ` +
1406
+ `"NoUserAgent_HEADER" not "NoUserAgent_Header"). Valid rule names: ` +
1407
+ `${validNames.join(", ")}.`);
1408
+ }
1409
+ }
1410
+ }
1411
+
1207
1412
  class JaypieApiGateway extends Construct {
1208
1413
  constructor(scope, id, props) {
1209
1414
  super(scope, id);
@@ -1393,12 +1598,12 @@ class JaypieLambda extends Construct {
1393
1598
  super(scope, id);
1394
1599
  const { allowAllOutbound, allowPublicSubnet, architecture = lambda.Architecture.X86_64, code, datadogApiKeyArn, deadLetterQueue, deadLetterQueueEnabled, deadLetterTopic, description, tables = [], environment: environmentInput, envSecrets = {}, ephemeralStorageSize, filesystem, handler = "index.handler", initialPolicy, layers = [], logGroup, logRetention = CDK$2.LAMBDA.LOG_RETENTION, maxEventAge, memorySize = CDK$2.LAMBDA.MEMORY_SIZE, paramsAndSecrets, paramsAndSecretsOptions, profiling, profilingGroup, provisionedConcurrentExecutions, reservedConcurrentExecutions, retryAttempts, roleTag = CDK$2.ROLE.PROCESSING, runtime = new lambda.Runtime("nodejs24.x", lambda.RuntimeFamily.NODEJS, {
1395
1600
  supportsInlineCode: true,
1396
- }), runtimeManagementMode, secrets: secretsInput = [], securityGroups, timeout = Duration.seconds(CDK$2.DURATION.LAMBDA_WORKER), tracing, vendorTag, vpc, vpcSubnets, } = props;
1601
+ }), runtimeManagementMode, secrets: secretsInput = [], securityGroups, serviceTag, timeout = Duration.seconds(CDK$2.DURATION.LAMBDA_WORKER), tracing, vendorTag, vpc, vpcSubnets, } = props;
1397
1602
  // Resolve environment from array or object syntax
1398
1603
  const initialEnvironment = resolveEnvironment(environmentInput);
1399
1604
  // Get base environment with defaults
1400
1605
  const environment = jaypieLambdaEnv({ initialEnvironment });
1401
- // Resolve secrets from mixed array (strings and JaypieEnvSecret instances)
1606
+ // Resolve secrets from mixed array (strings and JaypieSecret instances)
1402
1607
  // Use Stack.of(this) to ensure secrets are shared at stack level across all constructs
1403
1608
  const secrets = resolveSecrets(Stack.of(this), secretsInput);
1404
1609
  const codeAsset = typeof code === "string" ? lambda.Code.fromAsset(code) : code;
@@ -1409,7 +1614,7 @@ class JaypieLambda extends Construct {
1409
1614
  ...acc,
1410
1615
  [`SECRET_${key}`]: secret.secretName,
1411
1616
  }), {});
1412
- // Process JaypieEnvSecret array
1617
+ // Process JaypieSecret array
1413
1618
  const jaypieSecretsEnvironment = secrets.reduce((acc, secret) => {
1414
1619
  if (secret.envKey) {
1415
1620
  return {
@@ -1479,7 +1684,7 @@ class JaypieLambda extends Construct {
1479
1684
  Object.values(envSecrets).forEach((secret) => {
1480
1685
  secret.grantRead(this._lambda);
1481
1686
  });
1482
- // Grant read permissions for JaypieEnvSecrets
1687
+ // Grant read permissions for JaypieSecrets
1483
1688
  secrets.forEach((secret) => {
1484
1689
  secret.grantRead(this._lambda);
1485
1690
  });
@@ -1507,6 +1712,9 @@ class JaypieLambda extends Construct {
1507
1712
  if (roleTag) {
1508
1713
  Tags.of(this._lambda).add(CDK$2.TAG.ROLE, roleTag);
1509
1714
  }
1715
+ if (serviceTag) {
1716
+ Tags.of(this._lambda).add(CDK$2.TAG.SERVICE, serviceTag);
1717
+ }
1510
1718
  if (vendorTag) {
1511
1719
  Tags.of(this._lambda).add(CDK$2.TAG.VENDOR, vendorTag);
1512
1720
  }
@@ -1631,7 +1839,7 @@ class JaypieQueuedLambda extends Construct {
1631
1839
  super(scope, id);
1632
1840
  const { allowAllOutbound, allowPublicSubnet, architecture, batchSize = 1, code, datadogApiKeyArn, deadLetterQueue, deadLetterQueueEnabled, deadLetterTopic, description, environment = {}, envSecrets = {}, ephemeralStorageSize, fifo = true, filesystem, handler = "index.handler", initialPolicy, layers = [], logGroup, logRetention = CDK$2.LAMBDA.LOG_RETENTION, maxEventAge, memorySize = CDK$2.LAMBDA.MEMORY_SIZE, paramsAndSecrets, paramsAndSecretsOptions, profiling, profilingGroup, provisionedConcurrentExecutions, reservedConcurrentExecutions, retryAttempts, roleTag, runtime = new lambda.Runtime("nodejs24.x", lambda.RuntimeFamily.NODEJS, {
1633
1841
  supportsInlineCode: true,
1634
- }), runtimeManagementMode, secrets = [], securityGroups, tables = [], timeout = Duration.seconds(CDK$2.DURATION.LAMBDA_WORKER), tracing, vendorTag, visibilityTimeout = Duration.seconds(CDK$2.DURATION.LAMBDA_WORKER), vpc, vpcSubnets, } = props;
1842
+ }), runtimeManagementMode, secrets = [], securityGroups, serviceTag, tables = [], timeout = Duration.seconds(CDK$2.DURATION.LAMBDA_WORKER), tracing, vendorTag, visibilityTimeout = Duration.seconds(CDK$2.DURATION.LAMBDA_WORKER), vpc, vpcSubnets, } = props;
1635
1843
  // Create SQS Queue
1636
1844
  this._queue = new sqs.Queue(this, "Queue", {
1637
1845
  fifo,
@@ -1642,6 +1850,9 @@ class JaypieQueuedLambda extends Construct {
1642
1850
  if (roleTag) {
1643
1851
  Tags.of(this._queue).add(CDK$2.TAG.ROLE, roleTag);
1644
1852
  }
1853
+ if (serviceTag) {
1854
+ Tags.of(this._queue).add(CDK$2.TAG.SERVICE, serviceTag);
1855
+ }
1645
1856
  if (vendorTag) {
1646
1857
  Tags.of(this._queue).add(CDK$2.TAG.VENDOR, vendorTag);
1647
1858
  }
@@ -1679,6 +1890,7 @@ class JaypieQueuedLambda extends Construct {
1679
1890
  runtimeManagementMode,
1680
1891
  secrets,
1681
1892
  securityGroups,
1893
+ serviceTag,
1682
1894
  tables,
1683
1895
  timeout,
1684
1896
  tracing,
@@ -1873,7 +2085,7 @@ class JaypieBucketQueuedLambda extends JaypieQueuedLambda {
1873
2085
  constructor(scope, id, props) {
1874
2086
  props.fifo = false; // S3 event notifications are not supported for FIFO queues
1875
2087
  super(scope, id, props);
1876
- const { bucketName, roleTag, vendorTag, bucketOptions = {} } = props;
2088
+ const { bucketName, roleTag, serviceTag, vendorTag, bucketOptions = {}, } = props;
1877
2089
  // Create S3 Bucket
1878
2090
  this._bucket = new s3.Bucket(this, "Bucket", {
1879
2091
  bucketName: bucketOptions.bucketName || bucketName,
@@ -1884,6 +2096,9 @@ class JaypieBucketQueuedLambda extends JaypieQueuedLambda {
1884
2096
  if (roleTag) {
1885
2097
  Tags.of(this._bucket).add(CDK$2.TAG.ROLE, roleTag);
1886
2098
  }
2099
+ if (serviceTag) {
2100
+ Tags.of(this._bucket).add(CDK$2.TAG.SERVICE, serviceTag);
2101
+ }
1887
2102
  if (vendorTag) {
1888
2103
  Tags.of(this._bucket).add(CDK$2.TAG.VENDOR, vendorTag);
1889
2104
  }
@@ -2687,6 +2902,8 @@ class JaypieDistribution extends Construct {
2687
2902
  else {
2688
2903
  // Create new WebACL
2689
2904
  const { allow, managedRuleOverrides, managedRuleScopeDowns, managedRules = DEFAULT_MANAGED_RULES$1, rateLimitPerIp = DEFAULT_RATE_LIMIT$1, } = wafConfig;
2905
+ // Fail synth on rule names AWS WAF would silently ignore (#362)
2906
+ assertValidWafRuleNames({ allow, managedRuleOverrides });
2690
2907
  const allowEntries = allow
2691
2908
  ? Array.isArray(allow)
2692
2909
  ? allow
@@ -3721,7 +3938,7 @@ class JaypieNextJs extends Construct {
3721
3938
  ? path.join(process.cwd(), props.nextjsPath)
3722
3939
  : props?.nextjsPath || path.join(process.cwd(), "..", "nextjs");
3723
3940
  const paramsAndSecrets = resolveParamsAndSecrets();
3724
- // Resolve secrets from mixed array (strings and JaypieEnvSecret instances)
3941
+ // Resolve secrets from mixed array (strings and JaypieSecret instances)
3725
3942
  // Use Stack.of(this) to ensure secrets are shared at stack level across all constructs
3726
3943
  const secrets = resolveSecrets(Stack.of(this), props?.secrets);
3727
3944
  // Process secrets environment variables
@@ -3729,7 +3946,7 @@ class JaypieNextJs extends Construct {
3729
3946
  ...acc,
3730
3947
  [`SECRET_${key}`]: secret.secretName,
3731
3948
  }), {});
3732
- // Process JaypieEnvSecret array
3949
+ // Process JaypieSecret array
3733
3950
  const jaypieSecretsEnvironment = secrets.reduce((acc, secret) => {
3734
3951
  if (secret.envKey) {
3735
3952
  return {
@@ -3804,7 +4021,7 @@ class JaypieNextJs extends Construct {
3804
4021
  Object.values(envSecrets).forEach((secret) => {
3805
4022
  secret.grantRead(nextjs.serverFunction.lambdaFunction);
3806
4023
  });
3807
- // Grant read permissions for JaypieEnvSecrets
4024
+ // Grant read permissions for JaypieSecrets
3808
4025
  secrets.forEach((secret) => {
3809
4026
  secret.grantRead(nextjs.serverFunction.lambdaFunction);
3810
4027
  });
@@ -4673,6 +4890,8 @@ class JaypieWebDeploymentBucket extends Construct {
4673
4890
  }
4674
4891
  else {
4675
4892
  const { managedRuleOverrides, managedRuleScopeDowns, managedRules = DEFAULT_MANAGED_RULES, rateLimitPerIp = DEFAULT_RATE_LIMIT, } = wafConfig;
4893
+ // Fail synth on rule names AWS WAF would silently ignore (#362)
4894
+ assertValidWafRuleNames({ managedRuleOverrides });
4676
4895
  let priority = 0;
4677
4896
  const rules = [];
4678
4897
  for (const ruleName of managedRules) {
@@ -5346,5 +5565,5 @@ class JaypieWebSocketTable extends Construct {
5346
5565
  }
5347
5566
  }
5348
5567
 
5349
- export { CDK$2 as CDK, JaypieAccountLoggingBucket, JaypieApiGateway, JaypieAppStack, JaypieBucketQueuedLambda, JaypieCertificate, JaypieDatadogBucket, JaypieDatadogForwarder, JaypieDatadogSecret, JaypieDistribution, JaypieDnsRecord, JaypieDynamoDb, JaypieEnvSecret, JaypieEventsRule, JaypieExpressLambda, JaypieGitHubDeployRole, JaypieHostedZone, JaypieInfrastructureStack, JaypieLambda, JaypieMigration, JaypieMongoDbSecret, JaypieNextJs, JaypieOpenAiSecret, JaypieOrganizationTrail, JaypieQueuedLambda, JaypieSsoPermissions, JaypieSsoSyncApplication, JaypieStack, JaypieStaticWebBucket, JaypieTraceSigningKeySecret, JaypieWebDeploymentBucket, JaypieWebSocket, JaypieWebSocketLambda, JaypieWebSocketTable, addDatadogLayers, clearAllCertificateCaches, clearAllSecretsCaches, clearCertificateCache, clearSecretsCache, constructEnvName, constructStackName, constructTagger, constructWafLogBucketName, ensureRoute53QueryLoggingPolicy, envHostname, extendDatadogRole, isEnv, isProductionEnv, isSandboxEnv, isValidHostname$1 as isValidHostname, isValidSubdomain, jaypieLambdaEnv, mergeDomain, resolveCertificate, resolveDatadogForwarderFunction, resolveDatadogLayers, resolveDatadogLoggingDestination, resolveEnvironment, resolveHostedZone, resolveParamsAndSecrets, resolveSecrets };
5568
+ export { AWS_MANAGED_RULE_GROUPS, CDK$2 as CDK, JaypieAccountLoggingBucket, JaypieApiGateway, JaypieAppStack, JaypieBucketQueuedLambda, JaypieCertificate, JaypieDatadogBucket, JaypieDatadogForwarder, JaypieDatadogSecret, JaypieDistribution, JaypieDnsRecord, JaypieDynamoDb, JaypieEnvSecret, JaypieEventsRule, JaypieExpressLambda, JaypieGitHubDeployRole, JaypieHostedZone, JaypieInfrastructureStack, JaypieLambda, JaypieMigration, JaypieMongoDbSecret, JaypieNextJs, JaypieOpenAiSecret, JaypieOrganizationTrail, JaypieQueuedLambda, JaypieSecret, JaypieSsoPermissions, JaypieSsoSyncApplication, JaypieStack, JaypieStaticWebBucket, JaypieTraceSigningKeySecret, JaypieWebDeploymentBucket, JaypieWebSocket, JaypieWebSocketLambda, JaypieWebSocketTable, addDatadogLayers, assertValidWafRuleNames, clearAllCertificateCaches, clearAllSecretsCaches, clearCertificateCache, clearSecretsCache, constructEnvName, constructStackName, constructTagger, constructWafLogBucketName, ensureRoute53QueryLoggingPolicy, envHostname, extendDatadogRole, isEnv, isProductionEnv, isSandboxEnv, isValidHostname$1 as isValidHostname, isValidSubdomain, jaypieLambdaEnv, mergeDomain, resolveCertificate, resolveDatadogForwarderFunction, resolveDatadogLayers, resolveDatadogLoggingDestination, resolveEnvironment, resolveHostedZone, resolveParamsAndSecrets, resolveSecrets };
5350
5569
  //# sourceMappingURL=index.js.map