aws-ec2: invalid or missing CIDR mask reports "NaN.NaN.NaN.NaN is not a valid IP Address", and /33 silently produces a wrong block
- Dominant language
- TypeScript
- Stars
- 12.9k
- Forks
- 4.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 83
Description
### Describe the bug
An invalid or missing CIDR mask is never validated, so it flows into the address arithmetic in `CidrBlock` and surfaces as an error naming an IP address the caller never wrote — or, for a mask above 32, produces a nonsensical block with no error at all.
Forgetting the mask is an easy mistake, and the resulting message gives no hint that the mask is the problem:
```
ec2.IpAddresses.cidr('10.0.0.0')
-> Error: NaN.NaN.NaN.NaN is not a valid IP Address
```
Nothing in that message points at the missing `/16`.
Full behaviour of `IpAddresses.cidr()` on aws-cdk-lib 2.263.0:
| input | result |
|---|---|
| `'10.0.0.0'` (no mask) | ❌ `NaN.NaN.NaN.NaN is not a valid IP Address` |
| `'10.0.0.0/'` | ❌ `NaN.NaN.NaN.NaN is not a valid IP Address` |
| `'10.0.0.0/abc'` | ❌ `NaN.NaN.NaN.NaN is not a valid IP Address` |
| `'10.0.0.0/-1'` | ❌ `512.0.0.0 is not a valid IP Address` |
| `'10.0.0.0/33'` | ⚠️ **no error** — silently becomes `9.255.255.255/33` |
The `/33` row is the worst of these: `new CidrBlock('10.0.0.0/33')` returns a block whose `cidr` is `9.255.255.255/33`, an address outside the range the caller asked for, and synthesis continues. The failure only appears later — either as a confusing downstream error (`1 of /24 exceeds remaining space of 9.255.255.255/33`) or at deploy time from CloudFormation.
### Expected Behavior
An invalid mask should fail fast with an error that names the mask as the problem and states the accepted range, per the repo's error-message guidance (include the wrong value, the expected values, and what to change). Something like:
```
invalid CIDR mask in "10.0.0.0", expected an integer between 0 and 32 after a '/', e.g. '10.0.0.0/16'
```
### Current Behavior
The mask is read with `parseInt` and used without any validation:
```ts
// packages/aws-cdk-lib/aws-ec2/lib/network-util.ts
this.mask = parseInt(ipAddressOrCidr.split('/')[1], 10);
this.networkAddress = NetworkUtils.ipToNum(ipAddressOrCidr.split('/')[0]) +
CidrBlock.calculateNetsize(this.mask) - 1;
```
With no mask, `parseInt(undefined, 10)` is `NaN`, so `calculateNetsize` returns `2 ** (32 - NaN)` = `NaN`, `networkAddress` becomes `NaN`, and `numToIp` finally reports `NaN.NaN.NaN.NaN`. With `/33`, `calculateNetsize` returns `0.5`, and the block silently resolves one address below the input.
### Reproduction Steps
```ts
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
const app = new cdk.App();
const stack = new cdk.Stack(app, 'S');
// Forgot the mask
new ec2.Vpc(stack, 'V', {
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0'), // -> NaN.NaN.NaN.NaN is not a valid IP Address
});
```
Or, isolating the mask handling with no CDK app at all — this is the exact arithmetic from `network-util.ts`:
```js
const mask = parseInt('10.0.0.0'.split('/')[1], 10); // NaN
const netsize = 2 ** (32 - mask); // NaN
// -> networkAddress NaN -> numToIp(NaN) -> 'NaN.NaN.NaN.NaN'
const mask33 = parseInt('10.0.0.0/33'.split('/')[1], 10); // 33
const netsize33 = 2 ** (32 - mask33); // 0.5 <-- half an address
```
### Possible Solution
Validate the mask in the `CidrBlock` constructor before it reaches the arithmetic, and throw an `UnscopedValidationError` naming the mask.
Keeping `parseInt` semantics for the parse itself matters for backwards compatibility: `'10.0.0.0/16abc'` and `'10.0.0.0/16.5'` resolve to `16` today and synthesize successfully, so they must keep working. Only masks that cannot produce a usable block (`NaN`, `< 0`, `> 32`) should be rejected. Under that rule no input that synthesizes today changes behaviour:
| input | before | after |
|---|---|---|
| `10.0.0.0/16` | `10.0.0.0/16` | `10.0.0.0/16` |
| `0.0.0.0/0` | `0.0.0.0/0` | `0.0.0.0/0` |
| `10.0.0.1/32` | `10.0.0.1/32` | `10.0.0.1/32` |
| `10.0.0.0/16abc` | `10.0.0.0/16` | `10.0.0.0/16` |
| `10.0.0.0/16.5` | `10.0.0.0/16` | `10.0.0.0/16` |
| `10.0.0.0/33` | `9.255.255.255/33` | clear error |
| `10.0.0.0/-1` | `512.0.0.0 is not a valid IP Address` | clear error |
| `10.0.0.0` | `NaN.NaN.NaN.NaN is not a valid IP Address` | clear error |
The only rows that change are ones that already failed, or that produced a block CloudFormation would reject — so no feature flag is needed under the contributing guidance for validation changes.
### Other Information
Related but distinct: #34784 covers misaligned *base addresses* being silently rounded up (`10.0.3.1/28` → `10.0.3.16/28`). This issue is about the *mask*, and is unaffected by that one.
Separately, and not proposed for change here: the octet check in `NetworkUtils.validIp` uses `parseInt` per octet, so `'1.2.3.1e2/16'` is accepted and synthesizes a VPC with CidrBlock `1.3.0.0/16`. Tightening that would reject input that currently deploys, so it would need a feature flag — I've left it out to keep this to one concern.
### CDK CLI Version
N/A (synthesis-time, framework only)
### Framework Version
aws-cdk-lib 2.263.0
### Node.js Version
v24.1.0
### OS
macOS 15 (Darwin 25.2.0)
### Language
TypeScript
Contributor guide
Research direction
Start in packages/aws-cdk-lib/aws-ec2/lib/network-util.ts at the CidrBlock constructor and its callers such as IpAddresses.cidr(). Reproduce the listed missing, negative, and over-32 masks, then add focused regression coverage; done means invalid masks fail with a clear range-bearing validation error while existing parseInt-compatible masks still synthesize.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- cloud, networking
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100