@wraps.dev/cli 2.24.0 → 2.25.0

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/README.md CHANGED
@@ -502,7 +502,7 @@ DMARC (TXT)
502
502
  Policy for how receivers handle emails failing DKIM/SPF checks
503
503
 
504
504
  TXT _dmarc.example.com
505
- → v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com
505
+ → v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@example.com
506
506
  ```
507
507
 
508
508
  ## Hosting Provider Integration
Binary file
package/dist/cli.js CHANGED
@@ -3047,7 +3047,7 @@ async function previewDNSChanges(hostedZoneId, domain, dkimTokens, region, custo
3047
3047
  });
3048
3048
  const dmarcName = `_dmarc.${domain}`;
3049
3049
  const dmarcRuaDomain = mailFromDomain || domain;
3050
- const proposedDMARC = `"v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`;
3050
+ const proposedDMARC = `"v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`;
3051
3051
  const existingDMARC = findExistingRecord(existingRecords, dmarcName, "TXT");
3052
3052
  const existingDMARCValue = getRecordValue(existingDMARC);
3053
3053
  let dmarcStatus = "new";
@@ -3209,7 +3209,7 @@ async function createSelectedDNSRecords(hostedZoneId, domain, dkimTokens, region
3209
3209
  TTL: 1800,
3210
3210
  ResourceRecords: [
3211
3211
  {
3212
- Value: `"v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`
3212
+ Value: `"v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`
3213
3213
  }
3214
3214
  ]
3215
3215
  }
@@ -3332,7 +3332,7 @@ async function createDNSRecords(hostedZoneId, domain, dkimTokens, region, custom
3332
3332
  TTL: 1800,
3333
3333
  ResourceRecords: [
3334
3334
  {
3335
- Value: `"v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`
3335
+ Value: `"v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`
3336
3336
  }
3337
3337
  ]
3338
3338
  }
@@ -3556,7 +3556,7 @@ var init_cloudflare = __esm({
3556
3556
  const dmarcSuccess = await this.createRecord(
3557
3557
  `_dmarc.${domain}`,
3558
3558
  "TXT",
3559
- `v=DMARC1; p=quarantine; rua=mailto:postmaster@${mailFromDomain || domain}`
3559
+ `v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${mailFromDomain || domain}`
3560
3560
  );
3561
3561
  if (dmarcSuccess) {
3562
3562
  recordsCreated++;
@@ -3942,7 +3942,7 @@ var init_vercel = __esm({
3942
3942
  const dmarcSuccess = await this.createRecord(
3943
3943
  `_dmarc.${domain}`,
3944
3944
  "TXT",
3945
- `v=DMARC1; p=quarantine; rua=mailto:postmaster@${mailFromDomain || domain}`
3945
+ `v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${mailFromDomain || domain}`
3946
3946
  );
3947
3947
  if (dmarcSuccess) {
3948
3948
  recordsCreated++;
@@ -4211,7 +4211,7 @@ function buildEmailDNSRecords(data) {
4211
4211
  records.push({
4212
4212
  name: `_dmarc.${domain}`,
4213
4213
  type: "TXT",
4214
- value: `v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}`,
4214
+ value: `v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}`,
4215
4215
  category: "dmarc"
4216
4216
  });
4217
4217
  if (customTrackingDomain) {
@@ -6263,7 +6263,7 @@ Verification should complete within a few minutes.`,
6263
6263
  ),
6264
6264
  "",
6265
6265
  pc10.bold("DMARC Record (TXT):"),
6266
- ` ${pc10.cyan(`_dmarc.${domain}`)} ${pc10.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`
6266
+ ` ${pc10.cyan(`_dmarc.${domain}`)} ${pc10.dim("TXT")} "v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`
6267
6267
  );
6268
6268
  if (outputs.mailFromDomain) {
6269
6269
  dnsLines.push(
@@ -6492,7 +6492,7 @@ ${domainStrings.join("\n")}`);
6492
6492
  ),
6493
6493
  "",
6494
6494
  pc10.bold("DMARC Record (TXT):"),
6495
- ` ${pc10.cyan(`_dmarc.${domain.domain}`)} ${pc10.dim("TXT")} "v=DMARC1; p=quarantine; rua=mailto:postmaster@${dmarcRuaDomain}"`
6495
+ ` ${pc10.cyan(`_dmarc.${domain.domain}`)} ${pc10.dim("TXT")} "v=DMARC1; p=quarantine; sp=quarantine; np=reject; rua=mailto:postmaster@${dmarcRuaDomain}"`
6496
6496
  );
6497
6497
  }
6498
6498
  if (domain.mailFromDomain && domain.mailFromStatus !== "SUCCESS") {
@@ -15408,6 +15408,9 @@ async function checkDmarc(domain) {
15408
15408
  valid: false,
15409
15409
  policy: null,
15410
15410
  subdomainPolicy: null,
15411
+ nonExistentSubdomainPolicy: null,
15412
+ testing: false,
15413
+ psd: null,
15411
15414
  percentage: 100,
15412
15415
  reportingEnabled: false,
15413
15416
  ruaAddresses: [],
@@ -15460,6 +15463,21 @@ function parseDmarcRecord(record, result) {
15460
15463
  } else {
15461
15464
  result.subdomainPolicy = result.policy;
15462
15465
  }
15466
+ const np = tags.get("np");
15467
+ if (np) {
15468
+ if (["none", "quarantine", "reject"].includes(np)) {
15469
+ result.nonExistentSubdomainPolicy = np;
15470
+ } else {
15471
+ result.warnings.push(`Invalid non-existent subdomain policy (np): ${np}`);
15472
+ }
15473
+ }
15474
+ if (tags.get("t") === "y") {
15475
+ result.testing = true;
15476
+ }
15477
+ const psd = tags.get("psd");
15478
+ if (psd && ["y", "n", "u"].includes(psd)) {
15479
+ result.psd = psd;
15480
+ }
15463
15481
  const pct = tags.get("pct");
15464
15482
  if (pct) {
15465
15483
  const pctNum = Number.parseInt(pct, 10);
@@ -15468,7 +15486,9 @@ function parseDmarcRecord(record, result) {
15468
15486
  } else {
15469
15487
  result.percentage = pctNum;
15470
15488
  if (pctNum < 100) {
15471
- result.warnings.push(`Only ${pctNum}% of messages subject to policy`);
15489
+ result.warnings.push(
15490
+ `pct=${pctNum} is deprecated (DMARCbis receivers ignore it; enforcement is all-or-nothing). Use t=y while testing instead.`
15491
+ );
15472
15492
  }
15473
15493
  }
15474
15494
  }
@@ -15533,6 +15553,17 @@ function parseDmarcRecord(record, result) {
15533
15553
  if (result.subdomainPolicy === "none" && result.policy !== "none") {
15534
15554
  result.warnings.push("Subdomain policy is less strict than domain policy");
15535
15555
  }
15556
+ const enforcing = result.policy === "quarantine" || result.policy === "reject";
15557
+ if (result.testing && result.policy !== "none") {
15558
+ result.warnings.push(
15559
+ "t=y (testing) is set \u2014 receivers will not enforce the policy"
15560
+ );
15561
+ }
15562
+ if (enforcing && !result.nonExistentSubdomainPolicy) {
15563
+ result.warnings.push(
15564
+ "No np= set \u2014 add np=reject to block spoofing from non-existent subdomains (DMARCbis)"
15565
+ );
15566
+ }
15536
15567
  if (!result.reportingEnabled) {
15537
15568
  result.warnings.push("No aggregate reporting configured (rua=)");
15538
15569
  }
@@ -16686,11 +16717,18 @@ function collectDmarcIssues(dmarc, deductions, bonuses) {
16686
16717
  reason: "DMARC policy is none (not enforcing)"
16687
16718
  });
16688
16719
  }
16689
- if (dmarc.percentage < 100) {
16720
+ if (dmarc.testing && dmarc.policy !== "none") {
16690
16721
  deductions.push({
16691
16722
  check: "dmarc",
16692
16723
  points: 2,
16693
- reason: `DMARC pct=${dmarc.percentage} (not 100%)`
16724
+ reason: "DMARC testing mode (t=y) \u2014 receivers won't enforce the policy"
16725
+ });
16726
+ }
16727
+ if (dmarc.percentage < 100) {
16728
+ deductions.push({
16729
+ check: "dmarc",
16730
+ points: 1,
16731
+ reason: `DMARC pct=${dmarc.percentage} is deprecated (DMARCbis receivers ignore it; enforcement is inconsistent)`
16694
16732
  });
16695
16733
  }
16696
16734
  if (!dmarc.reportingEnabled) {
@@ -16707,6 +16745,13 @@ function collectDmarcIssues(dmarc, deductions, bonuses) {
16707
16745
  reason: "DMARC strict alignment configured"
16708
16746
  });
16709
16747
  }
16748
+ if ((dmarc.policy === "quarantine" || dmarc.policy === "reject") && dmarc.nonExistentSubdomainPolicy === "reject") {
16749
+ bonuses.push({
16750
+ check: "dmarc",
16751
+ points: 1,
16752
+ reason: "np=reject blocks non-existent subdomain spoofing (DMARCbis)"
16753
+ });
16754
+ }
16710
16755
  }
16711
16756
  function collectInfraIssues(checks, deductions, bonuses) {
16712
16757
  if (checks.mx.exists) {