@empire-builder-kit/nx 1.0.0-rc.33 → 1.0.0-rc.35

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 (56) hide show
  1. package/package.json +3 -3
  2. package/src/aws-authority/stage-authority.js +4 -0
  3. package/src/aws-authority/stage-authority.js.map +1 -1
  4. package/src/executors/deployment-plan/deployment-plan.js +3 -0
  5. package/src/executors/deployment-plan/deployment-plan.js.map +1 -1
  6. package/src/executors/github-bootstrap/github-bootstrap.js +1 -0
  7. package/src/executors/github-bootstrap/github-bootstrap.js.map +1 -1
  8. package/src/executors/local-deploy/local-deploy.d.ts +12 -3
  9. package/src/executors/local-deploy/local-deploy.js +156 -0
  10. package/src/executors/local-deploy/local-deploy.js.map +1 -1
  11. package/src/executors/local-deploy/schema.json +21 -0
  12. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.d.ts +13 -0
  13. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js +323 -137
  14. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js.map +1 -1
  15. package/src/executors/router-waf-rehearsal/schema.json +5 -0
  16. package/src/generators/preset/workspace-files-thin-deploy.d.ts +14 -1
  17. package/src/generators/preset/workspace-files-thin-deploy.js +897 -28
  18. package/src/generators/preset/workspace-files-thin-deploy.js.map +1 -1
  19. package/src/generators/preset/workspace-files-thin-docs.d.ts +2 -0
  20. package/src/generators/preset/workspace-files-thin-docs.js +51 -6
  21. package/src/generators/preset/workspace-files-thin-docs.js.map +1 -1
  22. package/src/generators/preset/workspace-files.d.ts +7 -1
  23. package/src/generators/preset/workspace-files.js +80 -18
  24. package/src/generators/preset/workspace-files.js.map +1 -1
  25. package/src/generators/repository-policy/repository-policy.js +1 -0
  26. package/src/generators/repository-policy/repository-policy.js.map +1 -1
  27. package/src/generators/repository-policy/schema.d.ts +2 -0
  28. package/src/generators/repository-policy/schema.json +26 -6
  29. package/src/generators/slice/built-in-plan-infrastructure-files.js +12 -0
  30. package/src/generators/slice/built-in-plan-infrastructure-files.js.map +1 -1
  31. package/src/generators/upgrade/upgrade.d.ts +12 -0
  32. package/src/generators/upgrade/upgrade.js +68 -0
  33. package/src/generators/upgrade/upgrade.js.map +1 -1
  34. package/src/git-policy/action-pins.d.ts +5 -0
  35. package/src/git-policy/action-pins.js +5 -0
  36. package/src/git-policy/action-pins.js.map +1 -1
  37. package/src/git-policy/github-bootstrap.d.ts +10 -2
  38. package/src/git-policy/github-bootstrap.js +129 -34
  39. package/src/git-policy/github-bootstrap.js.map +1 -1
  40. package/src/git-policy/routing.d.ts +5 -1
  41. package/src/git-policy/routing.js +50 -0
  42. package/src/git-policy/routing.js.map +1 -1
  43. package/src/repository-policy/policy.d.ts +12 -1
  44. package/src/repository-policy/policy.js +42 -2
  45. package/src/repository-policy/policy.js.map +1 -1
  46. package/src/schemas/repository-policy-v1.json +55 -16
  47. package/src/supply-chain/generated-container-release-command.js +281 -74
  48. package/src/supply-chain/generated-container-release-command.js.map +1 -1
  49. package/src/supply-chain/generated-files.js +10 -11
  50. package/src/supply-chain/generated-files.js.map +1 -1
  51. package/src/workspace-deployment/affected-lifecycle.d.ts +53 -0
  52. package/src/workspace-deployment/affected-lifecycle.js +84 -0
  53. package/src/workspace-deployment/affected-lifecycle.js.map +1 -0
  54. package/src/workspace-deployment/index.d.ts +1 -0
  55. package/src/workspace-deployment/index.js +1 -0
  56. package/src/workspace-deployment/index.js.map +1 -1
@@ -5,6 +5,7 @@ const client_cloudwatch_1 = require("@aws-sdk/client-cloudwatch");
5
5
  const client_cloudwatch_logs_1 = require("@aws-sdk/client-cloudwatch-logs");
6
6
  const client_cloudfront_1 = require("@aws-sdk/client-cloudfront");
7
7
  const client_sts_1 = require("@aws-sdk/client-sts");
8
+ const node_https_1 = require("node:https");
8
9
  const client_wafv2_1 = require("@aws-sdk/client-wafv2");
9
10
  const credential_providers_1 = require("@aws-sdk/credential-providers");
10
11
  const node_crypto_1 = require("node:crypto");
@@ -14,6 +15,7 @@ const devkit_1 = require("@nx/devkit");
14
15
  const index_js_1 = require("../../foundation/index.js");
15
16
  const router_waf_rate_rehearsal_js_1 = require("../../foundation/router-waf-rate-rehearsal.js");
16
17
  const index_js_2 = require("../../aws-authority/index.js");
18
+ const private_smoke_access_js_1 = require("../../foundation/private-smoke-access.js");
17
19
  const identity_js_1 = require("../../aws/identity.js");
18
20
  const index_js_3 = require("../../repository-policy/index.js");
19
21
  const deterministic_order_js_1 = require("../../deterministic-order.js");
@@ -772,6 +774,83 @@ function mergeRateEvidence(evidence, allowedRequestStatus, rateLimit, completedA
772
774
  probes: { ...probes, allowedRequestStatus, rateLimit },
773
775
  };
774
776
  }
777
+ const SEED_ATTESTATION_RETRIES = 12;
778
+ const SEED_ATTESTATION_INTERVAL_MS = 10_000;
779
+ function rawGet(url, headers) {
780
+ return new Promise((resolve, reject) => {
781
+ const request = (0, node_https_1.request)(url, { headers, method: 'GET', timeout: REQUEST_TIMEOUT_MS }, (response) => {
782
+ response.resume();
783
+ resolve({ status: response.statusCode ?? 0 });
784
+ });
785
+ request.on('timeout', () => request.destroy(new Error('timeout')));
786
+ request.on('error', reject);
787
+ request.end();
788
+ });
789
+ }
790
+ async function attestOperatorBlocks(input) {
791
+ // AWS WAF publishes sampled requests with a short delay; a seeded run
792
+ // probed moments ago, so its attestation waits a bounded number of times.
793
+ for (let attempt = 0;; attempt += 1) {
794
+ try {
795
+ await Promise.all([
796
+ attestOperatorBlock({
797
+ evidence: input.seed.probes
798
+ .managedRule,
799
+ stage: input.stage,
800
+ type: 'managed',
801
+ waf: input.waf,
802
+ webAclArn: input.seed.webAclArn,
803
+ }),
804
+ attestOperatorBlock({
805
+ evidence: input.seed.probes
806
+ .nonAllowlisted,
807
+ stage: input.stage,
808
+ type: 'nonallowlisted',
809
+ waf: input.waf,
810
+ webAclArn: input.seed.webAclArn,
811
+ }),
812
+ ]);
813
+ return;
814
+ }
815
+ catch (error) {
816
+ if (attempt >= input.retries)
817
+ throw error;
818
+ await input.sleep(SEED_ATTESTATION_INTERVAL_MS);
819
+ }
820
+ }
821
+ }
822
+ async function discoverRouterDistribution(input) {
823
+ const items = [];
824
+ let marker;
825
+ const seen = new Set();
826
+ do {
827
+ const page = await input.cloudFront.send(new client_cloudfront_1.ListDistributionsCommand({ Marker: marker }));
828
+ items.push(...(page.DistributionList?.Items ?? []));
829
+ marker = page.DistributionList?.NextMarker;
830
+ if (marker && seen.has(marker)) {
831
+ throw new Error('Router inventory pagination did not advance.');
832
+ }
833
+ if (marker)
834
+ seen.add(marker);
835
+ if (seen.size > 100) {
836
+ throw new Error('Router inventory exceeds bounded inspection.');
837
+ }
838
+ } while (marker);
839
+ const routers = items.filter((router) => router.DomainName === input.probeHost ||
840
+ router.Aliases?.Items?.includes(input.probeHost));
841
+ const router = routers[0];
842
+ if (routers.length !== 1 ||
843
+ !router?.Enabled ||
844
+ typeof router.Id !== 'string' ||
845
+ router.ARN !==
846
+ `arn:${input.partition}:cloudfront::${input.accountId}:distribution/${router.Id}` ||
847
+ typeof router.WebACLId !== 'string' ||
848
+ !WAF_ARN.test(router.WebACLId) ||
849
+ WAF_ARN.exec(router.WebACLId)?.[2] !== input.accountId) {
850
+ throw new Error('Managed rehearsal probe host does not resolve to one exact enabled Router distribution with an account-owned Web ACL.');
851
+ }
852
+ return { distributionId: router.Id, webAclArn: router.WebACLId };
853
+ }
775
854
  function createRouterWafRehearsalExecutor(dependencies = {}) {
776
855
  return async (options, context) => {
777
856
  try {
@@ -812,160 +891,267 @@ function createRouterWafRehearsalExecutor(dependencies = {}) {
812
891
  stage,
813
892
  });
814
893
  const evidencePath = (0, node_path_1.join)(root, EVIDENCE_PATH);
815
- const retainedEvidence = readBoundedJson(evidencePath, 'Router WAF live evidence');
816
- const seed = (0, index_js_1.validateRouterWafLiveEvidenceSeed)({
817
- evidence: retainedEvidence,
818
- policy,
819
- });
820
- if (seed.stage !== stage ||
821
- seed.accountId !== accountId ||
822
- seed.region !== region ||
823
- seed.deploymentRoleArn !== expectedRoleArn ||
824
- seed.workspace !== workspace ||
825
- seed.wafContractDigest !== wafContractDigest) {
826
- throw new Error('Router WAF live evidence stage, account, Region, deployment role, workspace, or WAF contract differs from committed authority.');
827
- }
828
- const invocationStartedAt = (dependencies.now ?? (() => new Date()))();
829
- if (!Number.isFinite(invocationStartedAt.getTime()) ||
830
- seed.startedAt > invocationStartedAt.getTime() ||
831
- seed.completedAt > invocationStartedAt.getTime() ||
832
- invocationStartedAt.getTime() - seed.completedAt >
833
- MAX_ROUTER_WAF_SEED_AGE_MS) {
834
- throw new Error('Router WAF live evidence seed is stale or future-dated; no live request was made.');
835
- }
836
- const receiptPath = (0, node_path_1.join)(root, RECEIPT_PATH);
837
- const retainedReceipt = (0, index_js_1.normalizeRouterWafBlockingRehearsal)(readBoundedJson(receiptPath, 'Router WAF blocking rehearsal receipt'));
838
- if (retainedReceipt) {
839
- throw new Error('Router WAF blocking rehearsal receipt must be reset to its exact pending seed before another live probe.');
840
- }
841
894
  const url = normalizedProbeUrl(options.probeUrl);
842
895
  const profile = selectedProfile(options, environment);
843
896
  const aws = clients(profile, region, dependencies);
897
+ const seedProbes = options.seedProbes === true;
898
+ const sleep = dependencies.sleep ??
899
+ ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
900
+ const seal = async () => {
901
+ const retainedEvidence = readBoundedJson(evidencePath, 'Router WAF live evidence');
902
+ const seed = (0, index_js_1.validateRouterWafLiveEvidenceSeed)({
903
+ evidence: retainedEvidence,
904
+ policy,
905
+ });
906
+ if (seed.stage !== stage ||
907
+ seed.accountId !== accountId ||
908
+ seed.region !== region ||
909
+ seed.deploymentRoleArn !== expectedRoleArn ||
910
+ seed.workspace !== workspace ||
911
+ seed.wafContractDigest !== wafContractDigest) {
912
+ throw new Error('Router WAF live evidence stage, account, Region, deployment role, workspace, or WAF contract differs from committed authority.');
913
+ }
914
+ const invocationStartedAt = (dependencies.now ?? (() => new Date()))();
915
+ if (!Number.isFinite(invocationStartedAt.getTime()) ||
916
+ seed.startedAt > invocationStartedAt.getTime() ||
917
+ seed.completedAt > invocationStartedAt.getTime() ||
918
+ invocationStartedAt.getTime() - seed.completedAt >
919
+ MAX_ROUTER_WAF_SEED_AGE_MS) {
920
+ throw new Error('Router WAF live evidence seed is stale or future-dated; no live request was made.');
921
+ }
922
+ const receiptPath = (0, node_path_1.join)(root, RECEIPT_PATH);
923
+ const retainedReceipt = (0, index_js_1.normalizeRouterWafBlockingRehearsal)(readBoundedJson(receiptPath, 'Router WAF blocking rehearsal receipt'));
924
+ if (retainedReceipt) {
925
+ throw new Error('Router WAF blocking rehearsal receipt must be reset to its exact pending seed before another live probe.');
926
+ }
927
+ const identity = await aws.sts.send(new client_sts_1.GetCallerIdentityCommand({}));
928
+ if (identity.Account !== accountId ||
929
+ typeof identity.Arn !== 'string' ||
930
+ !(0, identity_js_1.matchesExpectedAssumedRoleArn)(accountId, identity.Arn, expectedRoleArn)) {
931
+ throw new Error('Router WAF rehearsal caller does not match the committed account and exact stage deployment role.');
932
+ }
933
+ await (dependencies.ownership ?? index_js_4.assertWorkspaceStageOwnershipForWorkspace)(context.root, stage, profile ? { profile } : {});
934
+ const webAclVisibilityMetricName = (0, router_waf_rate_rehearsal_js_1.routerWafWebAclMetricName)(stage);
935
+ const [, webAclMetricName] = await Promise.all([
936
+ attestCloudFrontDistribution({
937
+ accountId,
938
+ cloudFront: aws.cloudFront,
939
+ distributionId: seed.distributionId,
940
+ partition,
941
+ probeHost: url.hostname,
942
+ stage,
943
+ webAclArn: seed.webAclArn,
944
+ workspace,
945
+ }),
946
+ attestRouterWafBaseline({
947
+ accountId,
948
+ cloudWatchLogs: aws.cloudWatchLogs,
949
+ plan,
950
+ policyDigest: (0, index_js_1.routerWafPolicyDigest)(policy),
951
+ stage,
952
+ waf: aws.waf,
953
+ webAclArn: seed.webAclArn,
954
+ webAclMetricName: webAclVisibilityMetricName,
955
+ workspace,
956
+ }),
957
+ ]);
958
+ await attestOperatorBlocks({
959
+ retries: seedProbes ? SEED_ATTESTATION_RETRIES : 0,
960
+ seed,
961
+ sleep,
962
+ stage,
963
+ waf: aws.waf,
964
+ });
965
+ const fetchRequest = dependencies.fetch ?? fetch;
966
+ const baselineResponse = await fetchRequest(url, {
967
+ cache: 'no-store',
968
+ headers: { 'cache-control': 'no-cache' },
969
+ redirect: 'manual',
970
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
971
+ });
972
+ const allowedRequestStatus = baselineResponse.status;
973
+ await baselineResponse.body?.cancel();
974
+ if (allowedRequestStatus < 200 || allowedRequestStatus > 399) {
975
+ throw new Error(`Router WAF rehearsal health preflight returned unexpected HTTP ${allowedRequestStatus}; rate traffic was not sent.`);
976
+ }
977
+ const nonce = (dependencies.createNonce ?? (() => (0, node_crypto_1.randomBytes)(32).toString('hex')))();
978
+ if (!RATE_MARKER_NONCE_PATTERN.test(nonce)) {
979
+ throw new Error('Router WAF rehearsal invocation nonce is not one opaque 256-bit lowercase hexadecimal value.');
980
+ }
981
+ const rateLimit = await (dependencies.runRateRehearsal ?? router_waf_rate_rehearsal_js_1.runRouterWafRateRehearsal)({
982
+ dependencies: {
983
+ ...(dependencies.now ? { now: dependencies.now } : {}),
984
+ observe: ({ endTime, markerDigest: expectedMarkerDigest, ruleMetricName, sequence, startTime, webAclMetricName: expectedWebAclMetricName, }) => {
985
+ const marker = rateMarker(nonce, sequence);
986
+ if (expectedWebAclMetricName !== webAclMetricName) {
987
+ throw new Error('Router WAF rate observation metric dimension differs from the attested physical Web ACL name.');
988
+ }
989
+ if (markerDigest(marker) !== expectedMarkerDigest) {
990
+ throw new Error('Router WAF rate observation marker differs from the exact blocked request.');
991
+ }
992
+ return observeRateLimit({
993
+ cloudWatch: aws.cloudWatch,
994
+ endTime,
995
+ marker,
996
+ markerDigest: expectedMarkerDigest,
997
+ probePath: url.pathname,
998
+ ruleMetricName,
999
+ startTime,
1000
+ waf: aws.waf,
1001
+ webAclArn: seed.webAclArn,
1002
+ webAclMetricName: expectedWebAclMetricName,
1003
+ });
1004
+ },
1005
+ request: async (sequence) => {
1006
+ const marker = rateMarker(nonce, sequence);
1007
+ const response = await fetchRequest(url, {
1008
+ cache: 'no-store',
1009
+ headers: {
1010
+ 'cache-control': 'no-cache',
1011
+ [RATE_MARKER_HEADER]: marker,
1012
+ },
1013
+ redirect: 'manual',
1014
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
1015
+ });
1016
+ await response.body?.cancel();
1017
+ return {
1018
+ markerDigest: markerDigest(marker),
1019
+ status: response.status,
1020
+ };
1021
+ },
1022
+ ...(dependencies.sleep ? { sleep: dependencies.sleep } : {}),
1023
+ },
1024
+ rateLimitPerIp: policy.rateLimitPerIp,
1025
+ stage,
1026
+ webAclMetricName,
1027
+ });
1028
+ const completedAt = (dependencies.now ?? (() => new Date()))().toISOString();
1029
+ const evidence = mergeRateEvidence(retainedEvidence, allowedRequestStatus, rateLimit, completedAt);
1030
+ const receipt = (0, index_js_1.createRouterWafBlockingRehearsal)({ evidence, policy });
1031
+ writeBoundedJson(evidencePath, evidence, 'Router WAF live evidence');
1032
+ writeBoundedJson(receiptPath, receipt, 'Router WAF blocking rehearsal receipt');
1033
+ devkit_1.logger.info(JSON.stringify({
1034
+ evidenceDigest: receipt.evidenceDigest,
1035
+ accountId: receipt.accountId,
1036
+ policyDigest: receipt.policyDigest,
1037
+ rateRequests: rateLimit.requestsSent,
1038
+ region: receipt.region,
1039
+ sealed: true,
1040
+ stage: receipt.stage,
1041
+ }));
1042
+ };
1043
+ if (!seedProbes) {
1044
+ await seal();
1045
+ return { success: true };
1046
+ }
1047
+ // Managed rehearsal: the protected dispatch performs the operator probes
1048
+ // itself. The non-allowlisted probe runs before the runner is admitted;
1049
+ // the managed-rule probe and the rest of the rehearsal run inside the
1050
+ // temporary allowlist admission that private smoke already governs.
1051
+ if (environment.GITHUB_ACTIONS !== 'true' || stage !== 'staging') {
1052
+ throw new Error('Managed Router WAF rehearsal seeding runs only from the protected GitHub staging dispatch.');
1053
+ }
1054
+ const pendingEvidence = readBoundedJson(evidencePath, 'Router WAF live evidence');
1055
+ if (pendingEvidence?.status !== 'pending') {
1056
+ throw new Error('Managed Router WAF rehearsal seeding requires the exact pending live evidence seed; reset both seeds before another rehearsal.');
1057
+ }
1058
+ const sourceCommit = environment.EBK_SOURCE_COMMIT?.trim() ||
1059
+ environment.GITHUB_SHA?.trim() ||
1060
+ '';
1061
+ if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(sourceCommit)) {
1062
+ throw new Error('Managed Router WAF rehearsal seeding requires the exact protected source commit.');
1063
+ }
844
1064
  const identity = await aws.sts.send(new client_sts_1.GetCallerIdentityCommand({}));
845
1065
  if (identity.Account !== accountId ||
846
1066
  typeof identity.Arn !== 'string' ||
847
1067
  !(0, identity_js_1.matchesExpectedAssumedRoleArn)(accountId, identity.Arn, expectedRoleArn)) {
848
1068
  throw new Error('Router WAF rehearsal caller does not match the committed account and exact stage deployment role.');
849
1069
  }
850
- await (dependencies.ownership ?? index_js_4.assertWorkspaceStageOwnershipForWorkspace)(context.root, stage, profile ? { profile } : {});
851
- const webAclVisibilityMetricName = (0, router_waf_rate_rehearsal_js_1.routerWafWebAclMetricName)(stage);
852
- const [, webAclMetricName] = await Promise.all([
853
- attestCloudFrontDistribution({
854
- accountId,
855
- cloudFront: aws.cloudFront,
856
- distributionId: seed.distributionId,
857
- partition,
858
- probeHost: url.hostname,
859
- stage,
860
- webAclArn: seed.webAclArn,
861
- workspace,
862
- }),
863
- attestRouterWafBaseline({
864
- accountId,
865
- cloudWatchLogs: aws.cloudWatchLogs,
866
- plan,
867
- policyDigest: (0, index_js_1.routerWafPolicyDigest)(policy),
868
- stage,
869
- waf: aws.waf,
870
- webAclArn: seed.webAclArn,
871
- webAclMetricName: webAclVisibilityMetricName,
872
- workspace,
873
- }),
874
- ]);
875
- await Promise.all([
876
- attestOperatorBlock({
877
- evidence: seed.probes.managedRule,
878
- stage,
879
- type: 'managed',
880
- waf: aws.waf,
881
- webAclArn: seed.webAclArn,
882
- }),
883
- attestOperatorBlock({
884
- evidence: seed.probes
885
- .nonAllowlisted,
886
- stage,
887
- type: 'nonallowlisted',
888
- waf: aws.waf,
889
- webAclArn: seed.webAclArn,
890
- }),
891
- ]);
892
- const fetchRequest = dependencies.fetch ?? fetch;
893
- const baselineResponse = await fetchRequest(url, {
894
- cache: 'no-store',
895
- headers: { 'cache-control': 'no-cache' },
896
- redirect: 'manual',
897
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
1070
+ const discovered = await discoverRouterDistribution({
1071
+ accountId,
1072
+ cloudFront: aws.cloudFront,
1073
+ partition,
1074
+ probeHost: url.hostname,
898
1075
  });
899
- const allowedRequestStatus = baselineResponse.status;
900
- await baselineResponse.body?.cancel();
901
- if (allowedRequestStatus < 200 || allowedRequestStatus > 399) {
902
- throw new Error(`Router WAF rehearsal health preflight returned unexpected HTTP ${allowedRequestStatus}; rate traffic was not sent.`);
903
- }
1076
+ const now = dependencies.now ?? (() => new Date());
1077
+ const get = dependencies.rawGet ?? rawGet;
904
1078
  const nonce = (dependencies.createNonce ?? (() => (0, node_crypto_1.randomBytes)(32).toString('hex')))();
905
1079
  if (!RATE_MARKER_NONCE_PATTERN.test(nonce)) {
906
1080
  throw new Error('Router WAF rehearsal invocation nonce is not one opaque 256-bit lowercase hexadecimal value.');
907
1081
  }
908
- const rateLimit = await (dependencies.runRateRehearsal ?? router_waf_rate_rehearsal_js_1.runRouterWafRateRehearsal)({
909
- dependencies: {
910
- ...(dependencies.now ? { now: dependencies.now } : {}),
911
- observe: ({ endTime, markerDigest: expectedMarkerDigest, ruleMetricName, sequence, startTime, webAclMetricName: expectedWebAclMetricName, }) => {
912
- const marker = rateMarker(nonce, sequence);
913
- if (expectedWebAclMetricName !== webAclMetricName) {
914
- throw new Error('Router WAF rate observation metric dimension differs from the attested physical Web ACL name.');
915
- }
916
- if (markerDigest(marker) !== expectedMarkerDigest) {
917
- throw new Error('Router WAF rate observation marker differs from the exact blocked request.');
918
- }
919
- return observeRateLimit({
920
- cloudWatch: aws.cloudWatch,
921
- endTime,
922
- marker,
923
- markerDigest: expectedMarkerDigest,
924
- probePath: url.pathname,
925
- ruleMetricName,
926
- startTime,
927
- waf: aws.waf,
928
- webAclArn: seed.webAclArn,
929
- webAclMetricName: expectedWebAclMetricName,
930
- });
931
- },
932
- request: async (sequence) => {
933
- const marker = rateMarker(nonce, sequence);
934
- const response = await fetchRequest(url, {
935
- cache: 'no-store',
936
- headers: {
937
- 'cache-control': 'no-cache',
938
- [RATE_MARKER_HEADER]: marker,
939
- },
940
- redirect: 'manual',
941
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
942
- });
943
- await response.body?.cancel();
944
- return {
945
- markerDigest: markerDigest(marker),
946
- status: response.status,
947
- };
948
- },
949
- ...(dependencies.sleep ? { sleep: dependencies.sleep } : {}),
1082
+ const startedAt = now().toISOString();
1083
+ const operatorProbe = async (type, ruleName) => {
1084
+ const marker = `${nonce}:${type}`;
1085
+ const response = await get(url, {
1086
+ 'cache-control': 'no-cache',
1087
+ [OPERATOR_MARKER_HEADER]: marker,
1088
+ });
1089
+ const blockedAt = now().toISOString();
1090
+ if (response.status !== 403) {
1091
+ throw new Error(`Managed Router WAF ${type} probe returned HTTP ${response.status} instead of the expected 403 BLOCK.`);
1092
+ }
1093
+ return {
1094
+ blockedAt,
1095
+ marker,
1096
+ method: 'GET',
1097
+ path: url.pathname,
1098
+ ruleName,
1099
+ status: 403,
1100
+ };
1101
+ };
1102
+ const nonAllowlisted = await operatorProbe('nonallowlisted', 'StageIpAllowlist');
1103
+ await (dependencies.privateSmoke ?? private_smoke_access_js_1.runGithubPrivateSmoke)({
1104
+ account: accountId,
1105
+ deploymentProfile: operatingPolicy.deploymentProfile,
1106
+ root: context.root,
1107
+ run: async () => {
1108
+ const managedRule = await operatorProbe('managed', 'AWSManagedRulesCommonRuleSet#NoUserAgent_HEADER');
1109
+ const seedEvidence = {
1110
+ accountId,
1111
+ completedAt: now().toISOString(),
1112
+ deploymentRoleArn: expectedRoleArn,
1113
+ distributionId: discovered.distributionId,
1114
+ kind: 'ebk-router-waf-live-evidence',
1115
+ mode: 'block',
1116
+ policyDigest: (0, index_js_1.routerWafPolicyDigest)(policy),
1117
+ probes: {
1118
+ allowedRequestStatus: 200,
1119
+ cloudWatchMetricsObserved: true,
1120
+ countModeTuningObserved: false,
1121
+ managedRule,
1122
+ nonAllowlisted,
1123
+ rateLimit: null,
1124
+ sampledRequestsObserved: true,
1125
+ },
1126
+ region,
1127
+ requiredRules: [
1128
+ 'StageIpAllowlist',
1129
+ 'RateLimitPerIP',
1130
+ ...index_js_1.ROUTER_WAF_REQUIRED_MANAGED_RULES,
1131
+ ],
1132
+ result: 'passed',
1133
+ schemaVersion: index_js_1.ROUTER_WAF_LIVE_EVIDENCE_SCHEMA_VERSION,
1134
+ sourceCommit,
1135
+ stage,
1136
+ startedAt,
1137
+ wafContractDigest,
1138
+ webAclArn: discovered.webAclArn,
1139
+ webAclScope: 'CLOUDFRONT',
1140
+ workspace,
1141
+ };
1142
+ writeBoundedJson(evidencePath, seedEvidence, 'Router WAF live evidence');
1143
+ await seal();
950
1144
  },
951
- rateLimitPerIp: policy.rateLimitPerIp,
1145
+ source: sourceCommit,
952
1146
  stage,
953
- webAclMetricName,
1147
+ url: url.href,
1148
+ workspace,
1149
+ }, {
1150
+ ...(dependencies.environment
1151
+ ? { environment: dependencies.environment }
1152
+ : {}),
1153
+ ...(dependencies.fetch ? { fetch: dependencies.fetch } : {}),
954
1154
  });
955
- const completedAt = (dependencies.now ?? (() => new Date()))().toISOString();
956
- const evidence = mergeRateEvidence(retainedEvidence, allowedRequestStatus, rateLimit, completedAt);
957
- const receipt = (0, index_js_1.createRouterWafBlockingRehearsal)({ evidence, policy });
958
- writeBoundedJson(evidencePath, evidence, 'Router WAF live evidence');
959
- writeBoundedJson(receiptPath, receipt, 'Router WAF blocking rehearsal receipt');
960
- devkit_1.logger.info(JSON.stringify({
961
- evidenceDigest: receipt.evidenceDigest,
962
- accountId: receipt.accountId,
963
- policyDigest: receipt.policyDigest,
964
- rateRequests: rateLimit.requestsSent,
965
- region: receipt.region,
966
- sealed: true,
967
- stage: receipt.stage,
968
- }));
969
1155
  return { success: true };
970
1156
  }
971
1157
  catch (error) {