@intentius/chant-lexicon-aws 0.18.12 → 0.18.13

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 (126) hide show
  1. package/dist/composites/agentcore-agent.d.ts +74 -0
  2. package/dist/composites/agentcore-agent.d.ts.map +1 -0
  3. package/dist/composites/agentcore-trust-policy.d.ts +11 -0
  4. package/dist/composites/agentcore-trust-policy.d.ts.map +1 -0
  5. package/dist/composites/index.d.ts +4 -0
  6. package/dist/composites/index.d.ts.map +1 -1
  7. package/dist/composites/microvm-app.d.ts +116 -0
  8. package/dist/composites/microvm-app.d.ts.map +1 -0
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/integrity.json +22 -4
  12. package/dist/lint/audit-catalog.d.ts.map +1 -1
  13. package/dist/lint/post-synth/cf-refs.d.ts +14 -0
  14. package/dist/lint/post-synth/cf-refs.d.ts.map +1 -1
  15. package/dist/lint/post-synth/index.d.ts.map +1 -1
  16. package/dist/lint/post-synth/waw038.d.ts +12 -0
  17. package/dist/lint/post-synth/waw038.d.ts.map +1 -0
  18. package/dist/lint/post-synth/waw039.d.ts +11 -0
  19. package/dist/lint/post-synth/waw039.d.ts.map +1 -0
  20. package/dist/lint/post-synth/waw040.d.ts +13 -0
  21. package/dist/lint/post-synth/waw040.d.ts.map +1 -0
  22. package/dist/lint/post-synth/waw041.d.ts +10 -0
  23. package/dist/lint/post-synth/waw041.d.ts.map +1 -0
  24. package/dist/lint/post-synth/waw042.d.ts +12 -0
  25. package/dist/lint/post-synth/waw042.d.ts.map +1 -0
  26. package/dist/lint/post-synth/waw043.d.ts +11 -0
  27. package/dist/lint/post-synth/waw043.d.ts.map +1 -0
  28. package/dist/lint/post-synth/waw044.d.ts +18 -0
  29. package/dist/lint/post-synth/waw044.d.ts.map +1 -0
  30. package/dist/lint/post-synth/waw045.d.ts +11 -0
  31. package/dist/lint/post-synth/waw045.d.ts.map +1 -0
  32. package/dist/lint/post-synth/waw046.d.ts +12 -0
  33. package/dist/lint/post-synth/waw046.d.ts.map +1 -0
  34. package/dist/lint/post-synth/waw047.d.ts +11 -0
  35. package/dist/lint/post-synth/waw047.d.ts.map +1 -0
  36. package/dist/lint/post-synth/waw048.d.ts +11 -0
  37. package/dist/lint/post-synth/waw048.d.ts.map +1 -0
  38. package/dist/lint/post-synth/waw049.d.ts +21 -0
  39. package/dist/lint/post-synth/waw049.d.ts.map +1 -0
  40. package/dist/lint/post-synth/waw050.d.ts +11 -0
  41. package/dist/lint/post-synth/waw050.d.ts.map +1 -0
  42. package/dist/lint/post-synth/waw051.d.ts +11 -0
  43. package/dist/lint/post-synth/waw051.d.ts.map +1 -0
  44. package/dist/lint/post-synth/waw052.d.ts +12 -0
  45. package/dist/lint/post-synth/waw052.d.ts.map +1 -0
  46. package/dist/lint/post-synth/waw053.d.ts +10 -0
  47. package/dist/lint/post-synth/waw053.d.ts.map +1 -0
  48. package/dist/lint/post-synth/waw054.d.ts +11 -0
  49. package/dist/lint/post-synth/waw054.d.ts.map +1 -0
  50. package/dist/lint/post-synth/waw055.d.ts +11 -0
  51. package/dist/lint/post-synth/waw055.d.ts.map +1 -0
  52. package/dist/manifest.json +1 -1
  53. package/dist/op/activities/floci.d.ts +6 -0
  54. package/dist/op/activities/floci.d.ts.map +1 -1
  55. package/dist/plugin.d.ts.map +1 -1
  56. package/dist/rules/cf-refs.ts +23 -0
  57. package/dist/rules/waw038.ts +50 -0
  58. package/dist/rules/waw039.ts +51 -0
  59. package/dist/rules/waw040.ts +54 -0
  60. package/dist/rules/waw041.ts +48 -0
  61. package/dist/rules/waw042.ts +87 -0
  62. package/dist/rules/waw043.ts +57 -0
  63. package/dist/rules/waw044.ts +68 -0
  64. package/dist/rules/waw045.ts +58 -0
  65. package/dist/rules/waw046.ts +59 -0
  66. package/dist/rules/waw047.ts +50 -0
  67. package/dist/rules/waw048.ts +47 -0
  68. package/dist/rules/waw049.ts +100 -0
  69. package/dist/rules/waw050.ts +54 -0
  70. package/dist/rules/waw051.ts +49 -0
  71. package/dist/rules/waw052.ts +51 -0
  72. package/dist/rules/waw053.ts +53 -0
  73. package/dist/rules/waw054.ts +49 -0
  74. package/dist/rules/waw055.ts +49 -0
  75. package/dist/types/index.d.ts +11 -9
  76. package/package.json +2 -2
  77. package/src/composites/agentcore-agent.test.ts +188 -0
  78. package/src/composites/agentcore-agent.ts +195 -0
  79. package/src/composites/agentcore-trust-policy.ts +13 -0
  80. package/src/composites/index.ts +4 -0
  81. package/src/composites/microvm-app.test.ts +234 -0
  82. package/src/composites/microvm-app.ts +436 -0
  83. package/src/generated/index.d.ts +11 -9
  84. package/src/index.ts +2 -0
  85. package/src/lint/audit-catalog.ts +20 -0
  86. package/src/lint/post-synth/cf-refs.ts +23 -0
  87. package/src/lint/post-synth/index.ts +36 -0
  88. package/src/lint/post-synth/waw038.test.ts +78 -0
  89. package/src/lint/post-synth/waw038.ts +50 -0
  90. package/src/lint/post-synth/waw039.test.ts +72 -0
  91. package/src/lint/post-synth/waw039.ts +51 -0
  92. package/src/lint/post-synth/waw040.test.ts +71 -0
  93. package/src/lint/post-synth/waw040.ts +54 -0
  94. package/src/lint/post-synth/waw041.test.ts +72 -0
  95. package/src/lint/post-synth/waw041.ts +48 -0
  96. package/src/lint/post-synth/waw042.test.ts +138 -0
  97. package/src/lint/post-synth/waw042.ts +87 -0
  98. package/src/lint/post-synth/waw043.test.ts +84 -0
  99. package/src/lint/post-synth/waw043.ts +57 -0
  100. package/src/lint/post-synth/waw044.test.ts +112 -0
  101. package/src/lint/post-synth/waw044.ts +68 -0
  102. package/src/lint/post-synth/waw045.test.ts +101 -0
  103. package/src/lint/post-synth/waw045.ts +58 -0
  104. package/src/lint/post-synth/waw046.test.ts +112 -0
  105. package/src/lint/post-synth/waw046.ts +59 -0
  106. package/src/lint/post-synth/waw047.test.ts +64 -0
  107. package/src/lint/post-synth/waw047.ts +50 -0
  108. package/src/lint/post-synth/waw048.test.ts +77 -0
  109. package/src/lint/post-synth/waw048.ts +47 -0
  110. package/src/lint/post-synth/waw049.test.ts +139 -0
  111. package/src/lint/post-synth/waw049.ts +100 -0
  112. package/src/lint/post-synth/waw050.test.ts +75 -0
  113. package/src/lint/post-synth/waw050.ts +54 -0
  114. package/src/lint/post-synth/waw051.test.ts +78 -0
  115. package/src/lint/post-synth/waw051.ts +49 -0
  116. package/src/lint/post-synth/waw052.test.ts +54 -0
  117. package/src/lint/post-synth/waw052.ts +51 -0
  118. package/src/lint/post-synth/waw053.test.ts +59 -0
  119. package/src/lint/post-synth/waw053.ts +53 -0
  120. package/src/lint/post-synth/waw054.test.ts +49 -0
  121. package/src/lint/post-synth/waw054.ts +49 -0
  122. package/src/lint/post-synth/waw055.test.ts +43 -0
  123. package/src/lint/post-synth/waw055.ts +49 -0
  124. package/src/op/activities/floci.test.ts +26 -0
  125. package/src/op/activities/floci.ts +25 -9
  126. package/src/plugin.ts +3 -0
@@ -0,0 +1,59 @@
1
+ /**
2
+ * WAW046: ECS Secret-Looking Value In Plaintext Environment
3
+ *
4
+ * Flags ECS TaskDefinition container `Environment` entries whose name looks
5
+ * like a credential (password/secret/token/key/credential) — those values are
6
+ * visible in the task definition and console. They should be passed via
7
+ * `Secrets` (Secrets Manager / SSM Parameter Store) instead.
8
+ */
9
+
10
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
11
+ import { parseCFTemplate, getContainerDefinitions, isIntrinsic } from "./cf-refs";
12
+
13
+ const SECRET_NAME_PATTERN = /password|secret|token|api[_-]?key|credential/i;
14
+
15
+ export function checkEcsPlaintextSecrets(ctx: PostSynthContext): PostSynthDiagnostic[] {
16
+ const diagnostics: PostSynthDiagnostic[] = [];
17
+
18
+ for (const [_lexicon, output] of ctx.outputs) {
19
+ const template = parseCFTemplate(output);
20
+ if (!template?.Resources) continue;
21
+
22
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
23
+ if (resource.Type !== "AWS::ECS::TaskDefinition") continue;
24
+
25
+ for (const container of getContainerDefinitions(resource)) {
26
+ const env = container.Environment;
27
+ if (!Array.isArray(env)) continue;
28
+
29
+ for (const entry of env) {
30
+ if (typeof entry !== "object" || entry === null) continue;
31
+ const name = (entry as Record<string, unknown>).Name;
32
+ if (isIntrinsic(name) || typeof name !== "string") continue;
33
+
34
+ if (SECRET_NAME_PATTERN.test(name)) {
35
+ const containerName = typeof container.Name === "string" ? container.Name : "<unnamed>";
36
+ diagnostics.push({
37
+ checkId: "WAW046",
38
+ severity: "error",
39
+ message: `TaskDefinition "${logicalId}" container "${containerName}" passes "${name}" via plaintext Environment — use Secrets (Secrets Manager/SSM) instead`,
40
+ entity: logicalId,
41
+ lexicon: "aws",
42
+ });
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ return diagnostics;
50
+ }
51
+
52
+ export const waw046: PostSynthCheck = {
53
+ id: "WAW046",
54
+ description: "ECS container passes a secret-looking value via plaintext Environment instead of Secrets",
55
+
56
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
57
+ return checkEcsPlaintextSecrets(ctx);
58
+ },
59
+ };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * WAW047: ECS Privileged Container
3
+ *
4
+ * Flags ECS TaskDefinition containers running with Privileged: true — the
5
+ * container gets elevated access to the host, equivalent to root on the EC2
6
+ * instance (and rejected outright on Fargate).
7
+ */
8
+
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
+ import { parseCFTemplate, getContainerDefinitions, isIntrinsic } from "./cf-refs";
11
+
12
+ export function checkEcsPrivilegedContainer(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [_lexicon, output] of ctx.outputs) {
16
+ const template = parseCFTemplate(output);
17
+ if (!template?.Resources) continue;
18
+
19
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
20
+ if (resource.Type !== "AWS::ECS::TaskDefinition") continue;
21
+
22
+ for (const container of getContainerDefinitions(resource)) {
23
+ const privileged = container.Privileged;
24
+ if (isIntrinsic(privileged)) continue;
25
+
26
+ if (privileged === true) {
27
+ const containerName = typeof container.Name === "string" ? container.Name : "<unnamed>";
28
+ diagnostics.push({
29
+ checkId: "WAW047",
30
+ severity: "error",
31
+ message: `TaskDefinition "${logicalId}" container "${containerName}" runs with Privileged: true — drop elevated host access`,
32
+ entity: logicalId,
33
+ lexicon: "aws",
34
+ });
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ return diagnostics;
41
+ }
42
+
43
+ export const waw047: PostSynthCheck = {
44
+ id: "WAW047",
45
+ description: "ECS container runs privileged — drop elevated host access",
46
+
47
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
48
+ return checkEcsPrivilegedContainer(ctx);
49
+ },
50
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * WAW048: ECS Missing Log Configuration
3
+ *
4
+ * Flags ECS TaskDefinition containers with no LogConfiguration — container
5
+ * stdout/stderr is discarded, leaving no audit trail. Mirrors WAW024 (ALB
6
+ * access logging): advisory, not a hard block.
7
+ */
8
+
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
+ import { parseCFTemplate, getContainerDefinitions } from "./cf-refs";
11
+
12
+ export function checkEcsLogConfiguration(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [_lexicon, output] of ctx.outputs) {
16
+ const template = parseCFTemplate(output);
17
+ if (!template?.Resources) continue;
18
+
19
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
20
+ if (resource.Type !== "AWS::ECS::TaskDefinition") continue;
21
+
22
+ for (const container of getContainerDefinitions(resource)) {
23
+ if (container.LogConfiguration === undefined) {
24
+ const containerName = typeof container.Name === "string" ? container.Name : "<unnamed>";
25
+ diagnostics.push({
26
+ checkId: "WAW048",
27
+ severity: "warning",
28
+ message: `TaskDefinition "${logicalId}" container "${containerName}" has no LogConfiguration — container output is discarded`,
29
+ entity: logicalId,
30
+ lexicon: "aws",
31
+ });
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ return diagnostics;
38
+ }
39
+
40
+ export const waw048: PostSynthCheck = {
41
+ id: "WAW048",
42
+ description: "ECS container does not have a LogConfiguration — enable logging for audit trails",
43
+
44
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
45
+ return checkEcsLogConfiguration(ctx);
46
+ },
47
+ };
@@ -0,0 +1,100 @@
1
+ /**
2
+ * WAW049: Security Group Broad Ingress (any port, except ALB:80/443)
3
+ *
4
+ * Generalizes WAW019 beyond the four named sensitive ports (SSH/RDP/MySQL/
5
+ * Postgres): flags 0.0.0.0/0 or ::/0 ingress on *any* port, with a carve-out
6
+ * for the conventional public ALB listener ports (exactly 80 or 443).
7
+ *
8
+ * Port 80 is exempt unconditionally, not just on a relaxed tier (#917): a
9
+ * public ALB's HTTP listener is meant to be internet-facing on every tier —
10
+ * on the full/production tier it still needs an SG rule open to 0.0.0.0/0 so
11
+ * clients can reach the listener that redirects them to HTTPS (WAW044 is the
12
+ * rule that enforces the redirect actually happens; this rule only judges the
13
+ * SG's shape). The "light" tier serves plaintext HTTP straight off that same
14
+ * port instead of redirecting, which doesn't change the SG's exposure at all.
15
+ * A genuinely over-broad rule (all ports, SSH, etc.) still errors regardless
16
+ * of tier.
17
+ */
18
+
19
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
20
+ import { parseCFTemplate, getSecurityGroupIngress, isIntrinsic } from "./cf-refs";
21
+
22
+ const OPEN_CIDRS = new Set(["0.0.0.0/0", "::/0"]);
23
+ const ALB_LISTENER_PORTS = new Set([80, 443]);
24
+
25
+ function isAlbListenerException(rule: Record<string, unknown>): boolean {
26
+ return (
27
+ typeof rule.FromPort === "number" &&
28
+ rule.FromPort === rule.ToPort &&
29
+ ALB_LISTENER_PORTS.has(rule.FromPort)
30
+ );
31
+ }
32
+
33
+ function checkIngressRule(
34
+ rule: Record<string, unknown>,
35
+ logicalId: string,
36
+ diagnostics: PostSynthDiagnostic[],
37
+ ): void {
38
+ const cidrIp = rule.CidrIp;
39
+ const cidrIpv6 = rule.CidrIpv6;
40
+
41
+ const hasOpenCidr =
42
+ (typeof cidrIp === "string" && OPEN_CIDRS.has(cidrIp)) ||
43
+ (typeof cidrIpv6 === "string" && OPEN_CIDRS.has(cidrIpv6));
44
+
45
+ if (!hasOpenCidr) return;
46
+
47
+ // Can't statically verify a port pinned by an intrinsic — don't guess.
48
+ if (isIntrinsic(rule.FromPort) || isIntrinsic(rule.ToPort)) return;
49
+
50
+ if (isAlbListenerException(rule)) return;
51
+
52
+ const cidr = typeof cidrIp === "string" && OPEN_CIDRS.has(cidrIp) ? cidrIp : cidrIpv6;
53
+ const fromPort = rule.FromPort;
54
+ const toPort = rule.ToPort;
55
+ const portDesc =
56
+ fromPort !== undefined && toPort !== undefined ? ` on ports ${fromPort}-${toPort}` : " on all ports";
57
+
58
+ diagnostics.push({
59
+ checkId: "WAW049",
60
+ severity: "error",
61
+ message: `Security group "${logicalId}" allows unrestricted ingress from ${cidr}${portDesc} — restrict to specific CIDR ranges (only ALB:80/443 is exempt)`,
62
+ entity: logicalId,
63
+ lexicon: "aws",
64
+ });
65
+ }
66
+
67
+ export function checkBroadIngress(ctx: PostSynthContext): PostSynthDiagnostic[] {
68
+ const diagnostics: PostSynthDiagnostic[] = [];
69
+
70
+ for (const [_lexicon, output] of ctx.outputs) {
71
+ const template = parseCFTemplate(output);
72
+ if (!template?.Resources) continue;
73
+
74
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
75
+ if (resource.Type === "AWS::EC2::SecurityGroup") {
76
+ for (const rule of getSecurityGroupIngress(resource)) {
77
+ checkIngressRule(rule, logicalId, diagnostics);
78
+ }
79
+ }
80
+
81
+ if (resource.Type === "AWS::EC2::SecurityGroupIngress") {
82
+ const props = resource.Properties ?? {};
83
+ if (!isIntrinsic(props)) {
84
+ checkIngressRule(props as Record<string, unknown>, logicalId, diagnostics);
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ return diagnostics;
91
+ }
92
+
93
+ export const waw049: PostSynthCheck = {
94
+ id: "WAW049",
95
+ description: "Security group allows unrestricted ingress on a port other than ALB:80/443",
96
+
97
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
98
+ return checkBroadIngress(ctx);
99
+ },
100
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * WAW050: Cognito Advanced Security Disabled
3
+ *
4
+ * Flags Cognito UserPools without UserPoolAddOns.AdvancedSecurityMode set to
5
+ * AUDIT or ENFORCED — without it, Cognito does not evaluate risk (compromised
6
+ * credentials, adaptive auth) on sign-in/sign-up.
7
+ */
8
+
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
+ import { parseCFTemplate, isIntrinsic } from "./cf-refs";
11
+
12
+ export function checkCognitoAdvancedSecurity(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [_lexicon, output] of ctx.outputs) {
16
+ const template = parseCFTemplate(output);
17
+ if (!template?.Resources) continue;
18
+
19
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
20
+ if (resource.Type !== "AWS::Cognito::UserPool") continue;
21
+
22
+ const props = resource.Properties ?? {};
23
+ const addOns = props.UserPoolAddOns;
24
+
25
+ if (isIntrinsic(addOns)) continue;
26
+
27
+ const mode =
28
+ typeof addOns === "object" && addOns !== null ? (addOns as Record<string, unknown>).AdvancedSecurityMode : undefined;
29
+
30
+ if (isIntrinsic(mode)) continue;
31
+
32
+ if (mode !== "AUDIT" && mode !== "ENFORCED") {
33
+ diagnostics.push({
34
+ checkId: "WAW050",
35
+ severity: "error",
36
+ message: `Cognito UserPool "${logicalId}" does not have advanced security enabled — set UserPoolAddOns.AdvancedSecurityMode to AUDIT or ENFORCED`,
37
+ entity: logicalId,
38
+ lexicon: "aws",
39
+ });
40
+ }
41
+ }
42
+ }
43
+
44
+ return diagnostics;
45
+ }
46
+
47
+ export const waw050: PostSynthCheck = {
48
+ id: "WAW050",
49
+ description: "Cognito UserPool does not have advanced security (risk-based auth) enabled",
50
+
51
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
52
+ return checkCognitoAdvancedSecurity(ctx);
53
+ },
54
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * WAW051: Cognito Implicit OAuth Grant Allowed
3
+ *
4
+ * Flags UserPoolClients whose AllowedOAuthFlows include the deprecated
5
+ * "implicit" grant — it returns tokens directly in the redirect URL (exposed
6
+ * in browser history/referrer headers) instead of via a code exchange.
7
+ */
8
+
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
+ import { parseCFTemplate, isIntrinsic } from "./cf-refs";
11
+
12
+ export function checkCognitoImplicitGrant(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [_lexicon, output] of ctx.outputs) {
16
+ const template = parseCFTemplate(output);
17
+ if (!template?.Resources) continue;
18
+
19
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
20
+ if (resource.Type !== "AWS::Cognito::UserPoolClient") continue;
21
+
22
+ const props = resource.Properties ?? {};
23
+ const flows = props.AllowedOAuthFlows;
24
+
25
+ if (isIntrinsic(flows) || !Array.isArray(flows)) continue;
26
+
27
+ if (flows.some((f) => f === "implicit")) {
28
+ diagnostics.push({
29
+ checkId: "WAW051",
30
+ severity: "error",
31
+ message: `Cognito UserPoolClient "${logicalId}" allows the implicit OAuth grant — use the authorization code grant instead`,
32
+ entity: logicalId,
33
+ lexicon: "aws",
34
+ });
35
+ }
36
+ }
37
+ }
38
+
39
+ return diagnostics;
40
+ }
41
+
42
+ export const waw051: PostSynthCheck = {
43
+ id: "WAW051",
44
+ description: "Cognito UserPoolClient allows the deprecated implicit OAuth grant",
45
+
46
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
47
+ return checkCognitoImplicitGrant(ctx);
48
+ },
49
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * WAW052: Cognito MFA Not Required (full tier)
3
+ *
4
+ * Flags UserPools without MfaConfiguration: ON. Tier-aware (#894), same
5
+ * seam as WAW040: a relaxed "light" stack (local/Floci, or no --env/
6
+ * ownership.env set) only warns; the strict "full"/production tier
7
+ * (ctx.env === "prod"/"production"/"full") fails the build.
8
+ */
9
+
10
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
11
+ import { parseCFTemplate, isIntrinsic, isFullTierEnv } from "./cf-refs";
12
+
13
+ export function checkCognitoMfaRequired(ctx: PostSynthContext): PostSynthDiagnostic[] {
14
+ const diagnostics: PostSynthDiagnostic[] = [];
15
+ const severity = isFullTierEnv(ctx.env) ? "error" : "warning";
16
+
17
+ for (const [_lexicon, output] of ctx.outputs) {
18
+ const template = parseCFTemplate(output);
19
+ if (!template?.Resources) continue;
20
+
21
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
22
+ if (resource.Type !== "AWS::Cognito::UserPool") continue;
23
+
24
+ const props = resource.Properties ?? {};
25
+ const mfaConfiguration = props.MfaConfiguration;
26
+
27
+ if (isIntrinsic(mfaConfiguration)) continue;
28
+
29
+ if (mfaConfiguration !== "ON") {
30
+ diagnostics.push({
31
+ checkId: "WAW052",
32
+ severity,
33
+ message: `Cognito UserPool "${logicalId}" does not require MFA (MfaConfiguration: ON) — require it on the full/production tier`,
34
+ entity: logicalId,
35
+ lexicon: "aws",
36
+ });
37
+ }
38
+ }
39
+ }
40
+
41
+ return diagnostics;
42
+ }
43
+
44
+ export const waw052: PostSynthCheck = {
45
+ id: "WAW052",
46
+ description: "Cognito UserPool does not require MFA (full tier)",
47
+
48
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
49
+ return checkCognitoMfaRequired(ctx);
50
+ },
51
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * WAW053: ECR Image Scanning Disabled
3
+ *
4
+ * Flags ECR repositories without ImageScanningConfiguration.ScanOnPush: true
5
+ * — pushed images aren't scanned for known CVEs.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { parseCFTemplate, isIntrinsic } from "./cf-refs";
10
+
11
+ export function checkEcrScanOnPush(ctx: PostSynthContext): PostSynthDiagnostic[] {
12
+ const diagnostics: PostSynthDiagnostic[] = [];
13
+
14
+ for (const [_lexicon, output] of ctx.outputs) {
15
+ const template = parseCFTemplate(output);
16
+ if (!template?.Resources) continue;
17
+
18
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
19
+ if (resource.Type !== "AWS::ECR::Repository") continue;
20
+
21
+ const props = resource.Properties ?? {};
22
+ const scanConfig = props.ImageScanningConfiguration;
23
+
24
+ if (isIntrinsic(scanConfig)) continue;
25
+
26
+ const scanOnPush =
27
+ typeof scanConfig === "object" && scanConfig !== null ? (scanConfig as Record<string, unknown>).ScanOnPush : undefined;
28
+
29
+ if (isIntrinsic(scanOnPush)) continue;
30
+
31
+ if (scanOnPush !== true) {
32
+ diagnostics.push({
33
+ checkId: "WAW053",
34
+ severity: "error",
35
+ message: `ECR repository "${logicalId}" does not have ImageScanningConfiguration.ScanOnPush: true — pushed images aren't scanned for vulnerabilities`,
36
+ entity: logicalId,
37
+ lexicon: "aws",
38
+ });
39
+ }
40
+ }
41
+ }
42
+
43
+ return diagnostics;
44
+ }
45
+
46
+ export const waw053: PostSynthCheck = {
47
+ id: "WAW053",
48
+ description: "ECR repository does not scan images on push",
49
+
50
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
51
+ return checkEcrScanOnPush(ctx);
52
+ },
53
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * WAW054: ECR Tag Mutability Not Immutable
3
+ *
4
+ * Flags ECR repositories without ImageTagMutability: IMMUTABLE — mutable tags
5
+ * let a previously deployed/scanned/signed tag (e.g. "prod") silently point at
6
+ * different image content later.
7
+ */
8
+
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
+ import { parseCFTemplate, isIntrinsic } from "./cf-refs";
11
+
12
+ export function checkEcrTagImmutability(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [_lexicon, output] of ctx.outputs) {
16
+ const template = parseCFTemplate(output);
17
+ if (!template?.Resources) continue;
18
+
19
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
20
+ if (resource.Type !== "AWS::ECR::Repository") continue;
21
+
22
+ const props = resource.Properties ?? {};
23
+ const mutability = props.ImageTagMutability;
24
+
25
+ if (isIntrinsic(mutability)) continue;
26
+
27
+ if (mutability !== "IMMUTABLE") {
28
+ diagnostics.push({
29
+ checkId: "WAW054",
30
+ severity: "error",
31
+ message: `ECR repository "${logicalId}" does not have ImageTagMutability: IMMUTABLE — a tag can silently be repointed at different image content`,
32
+ entity: logicalId,
33
+ lexicon: "aws",
34
+ });
35
+ }
36
+ }
37
+ }
38
+
39
+ return diagnostics;
40
+ }
41
+
42
+ export const waw054: PostSynthCheck = {
43
+ id: "WAW054",
44
+ description: "ECR repository does not have immutable image tags",
45
+
46
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
47
+ return checkEcrTagImmutability(ctx);
48
+ },
49
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * WAW055: CloudWatch Logs Retention Not Set
3
+ *
4
+ * Flags Logs::LogGroup resources without RetentionInDays — an unset
5
+ * retention means "Never expire," which is rarely the intended cost/
6
+ * compliance posture.
7
+ */
8
+
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
+ import { parseCFTemplate, isIntrinsic } from "./cf-refs";
11
+
12
+ export function checkLogsRetention(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [_lexicon, output] of ctx.outputs) {
16
+ const template = parseCFTemplate(output);
17
+ if (!template?.Resources) continue;
18
+
19
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
20
+ if (resource.Type !== "AWS::Logs::LogGroup") continue;
21
+
22
+ const props = resource.Properties ?? {};
23
+ const retention = props.RetentionInDays;
24
+
25
+ if (isIntrinsic(retention)) continue;
26
+
27
+ if (retention === undefined) {
28
+ diagnostics.push({
29
+ checkId: "WAW055",
30
+ severity: "warning",
31
+ message: `Log group "${logicalId}" has no RetentionInDays set — logs are kept forever by default; set an explicit retention period`,
32
+ entity: logicalId,
33
+ lexicon: "aws",
34
+ });
35
+ }
36
+ }
37
+ }
38
+
39
+ return diagnostics;
40
+ }
41
+
42
+ export const waw055: PostSynthCheck = {
43
+ id: "WAW055",
44
+ description: "CloudWatch Logs log group has no retention period set",
45
+
46
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
47
+ return checkLogsRetention(ctx);
48
+ },
49
+ };
@@ -50316,7 +50316,7 @@ export declare class CfDistribution_CustomOriginConfig {
50316
50316
  HTTPSPort?: number;
50317
50317
  /** Specifies which IP protocol CloudFront uses when connecting to your origin. If your origin uses both IPv4 and IPv6 protocols, you can choose ``dualstack`` to help optimize reliability. */
50318
50318
  IpAddressType?: "dualstack" | "ipv4" | "ipv6";
50319
- /** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is 120 seconds, and the default (if you don't specify otherwise) is 5 seconds.
50319
+ /** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is 300 seconds, and the default (if you don't specify otherwise) is 5 seconds.
50320
50320
  For more information, see [Keep-alive timeout (custom origins only)](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesOrigin.html#DownloadDistValuesOriginKeepaliveTimeout) in the *Amazon CloudFront Developer Guide*. */
50321
50321
  OriginKeepaliveTimeout?: number;
50322
50322
  /** Configures mutual TLS authentication between CloudFront and your origin server. */
@@ -50795,7 +50795,7 @@ export declare class CfDistribution_VpcOriginConfig {
50795
50795
  constructor(props: {
50796
50796
  /** The VPC origin ID. */
50797
50797
  VpcOriginId: string;
50798
- /** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is 120 seconds, and the default (if you don't specify otherwise) is 5 seconds.
50798
+ /** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is 300 seconds, and the default (if you don't specify otherwise) is 5 seconds.
50799
50799
  For more information, see [Keep-alive timeout (custom origins only)](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesOrigin.html#DownloadDistValuesOriginKeepaliveTimeout) in the *Amazon CloudFront Developer Guide*. */
50800
50800
  OriginKeepaliveTimeout?: number;
50801
50801
  /** Specifies how long, in seconds, CloudFront waits for a response from the origin. This is also known as the *origin response timeout*. The minimum timeout is 1 second, the maximum is 120 seconds, and the default (if you don't specify otherwise) is 30 seconds.
@@ -50808,7 +50808,7 @@ export declare class CfDistribution_VpcOriginConfig {
50808
50808
 
50809
50809
  export declare class CfFunction_FunctionConfig {
50810
50810
  constructor(props: {
50811
- /** A comment to describe the function. */
50811
+ /** A comment to describe the function. The comment cannot be longer than 128 characters. */
50812
50812
  Comment: string;
50813
50813
  /** The function's runtime environment version. */
50814
50814
  Runtime: string;
@@ -54524,7 +54524,7 @@ export declare class ConnectionDrainingPolicy {
54524
54524
 
54525
54525
  export declare class ConnectionFunction_ConnectionFunctionConfig {
54526
54526
  constructor(props: {
54527
- /** A comment to describe the function. */
54527
+ /** A comment to describe the function. The comment cannot be longer than 128 characters. */
54528
54528
  Comment: string;
54529
54529
  /** The function's runtime environment version. */
54530
54530
  Runtime: "cloudfront-js-2.0";
@@ -54560,7 +54560,7 @@ export declare class ConnectionFunctionAssociation {
54560
54560
 
54561
54561
  export declare class ConnectionFunctionConfig {
54562
54562
  constructor(props: {
54563
- /** A comment to describe the function. */
54563
+ /** A comment to describe the function. The comment cannot be longer than 128 characters. */
54564
54564
  Comment: string;
54565
54565
  /** The function's runtime environment version. */
54566
54566
  Runtime: "cloudfront-js-2.0";
@@ -57733,7 +57733,7 @@ export declare class CustomOriginConfig {
57733
57733
  HTTPSPort?: number;
57734
57734
  /** Specifies which IP protocol CloudFront uses when connecting to your origin. If your origin uses both IPv4 and IPv6 protocols, you can choose ``dualstack`` to help optimize reliability. */
57735
57735
  IpAddressType?: "dualstack" | "ipv4" | "ipv6";
57736
- /** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is 120 seconds, and the default (if you don't specify otherwise) is 5 seconds.
57736
+ /** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is 300 seconds, and the default (if you don't specify otherwise) is 5 seconds.
57737
57737
  For more information, see [Keep-alive timeout (custom origins only)](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesOrigin.html#DownloadDistValuesOriginKeepaliveTimeout) in the *Amazon CloudFront Developer Guide*. */
57738
57738
  OriginKeepaliveTimeout?: number;
57739
57739
  /** Configures mutual TLS authentication between CloudFront and your origin server. */
@@ -72661,7 +72661,7 @@ export declare class FunctionAssociation {
72661
72661
 
72662
72662
  export declare class FunctionConfig {
72663
72663
  constructor(props: {
72664
- /** A comment to describe the function. */
72664
+ /** A comment to describe the function. The comment cannot be longer than 128 characters. */
72665
72665
  Comment: string;
72666
72666
  /** The function's runtime environment version. */
72667
72667
  Runtime: string;
@@ -121683,6 +121683,7 @@ export declare class VpcOrigin_VpcOriginEndpointConfig {
121683
121683
  HTTPPort?: number;
121684
121684
  /** The HTTPS port of the CloudFront VPC origin endpoint configuration. The default value is ``443``. */
121685
121685
  HTTPSPort?: number;
121686
+ IpAddressType?: "dualstack" | "ipv4";
121686
121687
  /** The origin protocol policy for the CloudFront VPC origin endpoint configuration. */
121687
121688
  OriginProtocolPolicy?: string;
121688
121689
  /** Specifies the minimum SSL/TLS protocol that CloudFront uses when connecting to your origin over HTTPS. Valid values include ``SSLv3``, ``TLSv1``, ``TLSv1.1``, and ``TLSv1.2``.
@@ -121695,7 +121696,7 @@ export declare class VpcOriginConfig {
121695
121696
  constructor(props: {
121696
121697
  /** The VPC origin ID. */
121697
121698
  VpcOriginId: string;
121698
- /** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is 120 seconds, and the default (if you don't specify otherwise) is 5 seconds.
121699
+ /** Specifies how long, in seconds, CloudFront persists its connection to the origin. The minimum timeout is 1 second, the maximum is 300 seconds, and the default (if you don't specify otherwise) is 5 seconds.
121699
121700
  For more information, see [Keep-alive timeout (custom origins only)](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesOrigin.html#DownloadDistValuesOriginKeepaliveTimeout) in the *Amazon CloudFront Developer Guide*. */
121700
121701
  OriginKeepaliveTimeout?: number;
121701
121702
  /** Specifies how long, in seconds, CloudFront waits for a response from the origin. This is also known as the *origin response timeout*. The minimum timeout is 1 second, the maximum is 120 seconds, and the default (if you don't specify otherwise) is 30 seconds.
@@ -121716,6 +121717,7 @@ export declare class VpcOriginEndpointConfig {
121716
121717
  HTTPPort?: number;
121717
121718
  /** The HTTPS port of the CloudFront VPC origin endpoint configuration. The default value is ``443``. */
121718
121719
  HTTPSPort?: number;
121720
+ IpAddressType?: "dualstack" | "ipv4";
121719
121721
  /** The origin protocol policy for the CloudFront VPC origin endpoint configuration. */
121720
121722
  OriginProtocolPolicy?: string;
121721
121723
  /** Specifies the minimum SSL/TLS protocol that CloudFront uses when connecting to your origin over HTTPS. Valid values include ``SSLv3``, ``TLSv1``, ``TLSv1.1``, and ``TLSv1.2``.
@@ -127344,7 +127346,7 @@ export type EmailContact_EmailContactStatus = "active" | "inactive";
127344
127346
 
127345
127347
  export type EMRServerlessApplication_Architecture = "ARM64" | "X86_64";
127346
127348
 
127347
- export type EnforcedGuardrailConfiguration_ConfigOwner = "ACCOUNT";
127349
+ export type EnforcedGuardrailConfiguration_ConfigOwner = "ACCOUNT" | "ORGANIZATION";
127348
127350
 
127349
127351
  export type EnvironmentAccountConnection_Status = "CONNECTED" | "PENDING" | "REJECTED";
127350
127352