@hasna/uptime 0.1.10 → 0.1.11

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/CHANGELOG.md CHANGED
@@ -6,6 +6,27 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.11] - 2026-06-28
10
+
11
+ ### Changed
12
+
13
+ - Added Terraform outputs for log groups, web alarms, backup vault, and backup
14
+ plan, KMS key ARN, and secret refs so the AWS runbook can use output-driven
15
+ commands.
16
+ - Expanded the AWS deployment runbook restore drill with AWS Backup restore-job,
17
+ polling, staging mount-target, validation, and cleanup steps.
18
+ - Made the AWS runbook command blocks use explicit shell variables and
19
+ consistent Terraform working-directory flags.
20
+ - Hardened hosted target policy to normalize IPv4-mapped IPv6 literals before
21
+ rejecting loopback, private, link-local, metadata, carrier-grade NAT,
22
+ unspecified, and multicast IPv4 ranges.
23
+ - Hardened hosted target policy to reject the full IPv6 link-local `fe80::/10`
24
+ range.
25
+ - Scoped hosted import preview lookups by workspace so preview responses cannot
26
+ reveal monitors from another hosted workspace.
27
+ - Documented DNS resolution, redirect, and rebinding enforcement as required
28
+ gates before enabling hosted public probe execution.
29
+
9
30
  ## [0.1.10] - 2026-06-28
10
31
 
11
32
  ### Changed
package/dist/api.js CHANGED
@@ -276,13 +276,74 @@ function isDeniedIpv4(ip) {
276
276
  }
277
277
  function isDeniedIpv6(ip) {
278
278
  const normalized = ip.toLowerCase();
279
- return normalized === "::" || normalized === "::1" || normalized.startsWith("fe80:") || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("ff") || normalized.startsWith("::ffff:127.") || normalized.startsWith("::ffff:10.") || normalized.startsWith("::ffff:169.254.") || /^::ffff:172\.(1[6-9]|2\d|3[0-1])\./.test(normalized) || normalized.startsWith("::ffff:192.168.");
279
+ const mappedIpv4 = ipv4FromMappedIpv6(normalized);
280
+ if (mappedIpv4)
281
+ return isDeniedIpv4(mappedIpv4);
282
+ const words = parseIpv6Words(normalized);
283
+ return normalized === "::" || normalized === "::1" || words !== null && (words[0] & 65472) === 65152 || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("ff");
284
+ }
285
+ function ipv4FromMappedIpv6(ip) {
286
+ const words = parseIpv6Words(ip);
287
+ if (!words)
288
+ return null;
289
+ if (words[0] !== 0 || words[1] !== 0 || words[2] !== 0 || words[3] !== 0 || words[4] !== 0 || words[5] !== 65535) {
290
+ return null;
291
+ }
292
+ return [
293
+ words[6] >> 8,
294
+ words[6] & 255,
295
+ words[7] >> 8,
296
+ words[7] & 255
297
+ ].join(".");
298
+ }
299
+ function parseIpv6Words(value) {
300
+ let ip = value.toLowerCase();
301
+ const zoneIndex = ip.indexOf("%");
302
+ if (zoneIndex >= 0)
303
+ ip = ip.slice(0, zoneIndex);
304
+ if (ip.includes(".")) {
305
+ const lastColon = ip.lastIndexOf(":");
306
+ if (lastColon < 0)
307
+ return null;
308
+ const ipv4 = parseIpv4Words(ip.slice(lastColon + 1));
309
+ if (!ipv4)
310
+ return null;
311
+ ip = `${ip.slice(0, lastColon)}:${(ipv4[0] << 8 | ipv4[1]).toString(16)}:${(ipv4[2] << 8 | ipv4[3]).toString(16)}`;
312
+ }
313
+ const compressed = ip.split("::");
314
+ if (compressed.length > 2)
315
+ return null;
316
+ const left = parseIpv6Side(compressed[0]);
317
+ const right = compressed.length === 2 ? parseIpv6Side(compressed[1]) : [];
318
+ if (!left || !right)
319
+ return null;
320
+ if (compressed.length === 1)
321
+ return left.length === 8 ? left : null;
322
+ const missing = 8 - left.length - right.length;
323
+ if (missing < 1)
324
+ return null;
325
+ return [...left, ...Array(missing).fill(0), ...right];
326
+ }
327
+ function parseIpv6Side(value) {
328
+ if (!value)
329
+ return [];
330
+ const words = value.split(":");
331
+ if (words.some((word) => !/^[0-9a-f]{1,4}$/.test(word)))
332
+ return null;
333
+ return words.map((word) => Number.parseInt(word, 16));
334
+ }
335
+ function parseIpv4Words(value) {
336
+ const words = value.split(".").map((part) => Number(part));
337
+ if (words.length !== 4 || words.some((word) => !Number.isInteger(word) || word < 0 || word > 255)) {
338
+ return null;
339
+ }
340
+ return words;
280
341
  }
281
342
 
282
343
  // src/imports.ts
283
- function previewImport(store, request) {
344
+ function previewImport(store, request, options = {}) {
284
345
  const source = normalizeSource(request.source);
285
- const items = dedupePreviewItems(request.records.map((record) => previewRecord(store, source, record, request.defaults ?? {})));
346
+ const items = dedupePreviewItems(request.records.map((record) => previewRecord(store, source, record, request.defaults ?? {}, options)));
286
347
  return {
287
348
  source,
288
349
  generatedAt: new Date().toISOString(),
@@ -356,7 +417,7 @@ function rollbackImport(store, batchId) {
356
417
  items
357
418
  };
358
419
  }
359
- function previewRecord(store, source, record, defaults) {
420
+ function previewRecord(store, source, record, defaults, options) {
360
421
  const warnings = [];
361
422
  let candidate;
362
423
  try {
@@ -376,13 +437,16 @@ function previewRecord(store, source, record, defaults) {
376
437
  reason: error instanceof Error ? error.message : String(error)
377
438
  };
378
439
  }
379
- const provenance = store.getProvenance(candidate.source, candidate.sourceId);
380
- const monitor = provenance ? store.getMonitor(provenance.monitorId) : store.getMonitor(candidate.name);
381
- if (provenance && !monitor) {
440
+ const monitorOptions = options.workspaceId ? { workspaceId: options.workspaceId } : undefined;
441
+ const rawProvenance = store.getProvenance(candidate.source, candidate.sourceId);
442
+ const provenanceMonitor = rawProvenance ? store.getMonitor(rawProvenance.monitorId, monitorOptions) : null;
443
+ const provenance = provenanceMonitor ? rawProvenance : null;
444
+ const monitor = provenanceMonitor ?? store.getMonitor(candidate.name, monitorOptions);
445
+ if (rawProvenance && !provenanceMonitor && !options.workspaceId) {
382
446
  return { candidate, action: "create", monitor: null, provenance, warnings: ["source provenance points to a missing monitor"], reason: null };
383
447
  }
384
448
  if (provenance && monitor) {
385
- const nameOwner = store.getMonitor(candidate.name);
449
+ const nameOwner = store.getMonitor(candidate.name, monitorOptions);
386
450
  if (nameOwner && nameOwner.id !== monitor.id) {
387
451
  return {
388
452
  candidate,
@@ -2729,8 +2793,8 @@ class UptimeService {
2729
2793
  const execute = () => this.submitProbeResultInTransaction(input);
2730
2794
  return this.store.runInTransaction ? this.store.runInTransaction(execute) : execute();
2731
2795
  }
2732
- previewImport(request) {
2733
- return previewImport(this.store, request);
2796
+ previewImport(request, options = {}) {
2797
+ return previewImport(this.store, request, options);
2734
2798
  }
2735
2799
  applyImport(request) {
2736
2800
  return applyImport(this.store, request);
@@ -3758,7 +3822,7 @@ async function handleApiRoute(service, request, url, apiPath, options, hosted, a
3758
3822
  return json(service.submitProbeResult(await jsonBody(request)), 201);
3759
3823
  }
3760
3824
  if (request.method === "POST" && apiPath === "/api/imports/preview") {
3761
- return json(service.previewImport(await jsonBody(request)));
3825
+ return json(service.previewImport(await jsonBody(request), { workspaceId: actor?.workspaceId }));
3762
3826
  }
3763
3827
  if (request.method === "POST" && apiPath === "/api/imports/apply") {
3764
3828
  if (hosted)
package/dist/cli/index.js CHANGED
@@ -2856,13 +2856,74 @@ function isDeniedIpv4(ip) {
2856
2856
  }
2857
2857
  function isDeniedIpv6(ip) {
2858
2858
  const normalized = ip.toLowerCase();
2859
- return normalized === "::" || normalized === "::1" || normalized.startsWith("fe80:") || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("ff") || normalized.startsWith("::ffff:127.") || normalized.startsWith("::ffff:10.") || normalized.startsWith("::ffff:169.254.") || /^::ffff:172\.(1[6-9]|2\d|3[0-1])\./.test(normalized) || normalized.startsWith("::ffff:192.168.");
2859
+ const mappedIpv4 = ipv4FromMappedIpv6(normalized);
2860
+ if (mappedIpv4)
2861
+ return isDeniedIpv4(mappedIpv4);
2862
+ const words = parseIpv6Words(normalized);
2863
+ return normalized === "::" || normalized === "::1" || words !== null && (words[0] & 65472) === 65152 || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("ff");
2864
+ }
2865
+ function ipv4FromMappedIpv6(ip) {
2866
+ const words = parseIpv6Words(ip);
2867
+ if (!words)
2868
+ return null;
2869
+ if (words[0] !== 0 || words[1] !== 0 || words[2] !== 0 || words[3] !== 0 || words[4] !== 0 || words[5] !== 65535) {
2870
+ return null;
2871
+ }
2872
+ return [
2873
+ words[6] >> 8,
2874
+ words[6] & 255,
2875
+ words[7] >> 8,
2876
+ words[7] & 255
2877
+ ].join(".");
2878
+ }
2879
+ function parseIpv6Words(value) {
2880
+ let ip = value.toLowerCase();
2881
+ const zoneIndex = ip.indexOf("%");
2882
+ if (zoneIndex >= 0)
2883
+ ip = ip.slice(0, zoneIndex);
2884
+ if (ip.includes(".")) {
2885
+ const lastColon = ip.lastIndexOf(":");
2886
+ if (lastColon < 0)
2887
+ return null;
2888
+ const ipv4 = parseIpv4Words(ip.slice(lastColon + 1));
2889
+ if (!ipv4)
2890
+ return null;
2891
+ ip = `${ip.slice(0, lastColon)}:${(ipv4[0] << 8 | ipv4[1]).toString(16)}:${(ipv4[2] << 8 | ipv4[3]).toString(16)}`;
2892
+ }
2893
+ const compressed = ip.split("::");
2894
+ if (compressed.length > 2)
2895
+ return null;
2896
+ const left = parseIpv6Side(compressed[0]);
2897
+ const right = compressed.length === 2 ? parseIpv6Side(compressed[1]) : [];
2898
+ if (!left || !right)
2899
+ return null;
2900
+ if (compressed.length === 1)
2901
+ return left.length === 8 ? left : null;
2902
+ const missing = 8 - left.length - right.length;
2903
+ if (missing < 1)
2904
+ return null;
2905
+ return [...left, ...Array(missing).fill(0), ...right];
2906
+ }
2907
+ function parseIpv6Side(value) {
2908
+ if (!value)
2909
+ return [];
2910
+ const words = value.split(":");
2911
+ if (words.some((word) => !/^[0-9a-f]{1,4}$/.test(word)))
2912
+ return null;
2913
+ return words.map((word) => Number.parseInt(word, 16));
2914
+ }
2915
+ function parseIpv4Words(value) {
2916
+ const words = value.split(".").map((part) => Number(part));
2917
+ if (words.length !== 4 || words.some((word) => !Number.isInteger(word) || word < 0 || word > 255)) {
2918
+ return null;
2919
+ }
2920
+ return words;
2860
2921
  }
2861
2922
 
2862
2923
  // src/imports.ts
2863
- function previewImport(store, request) {
2924
+ function previewImport(store, request, options = {}) {
2864
2925
  const source = normalizeSource(request.source);
2865
- const items = dedupePreviewItems(request.records.map((record) => previewRecord(store, source, record, request.defaults ?? {})));
2926
+ const items = dedupePreviewItems(request.records.map((record) => previewRecord(store, source, record, request.defaults ?? {}, options)));
2866
2927
  return {
2867
2928
  source,
2868
2929
  generatedAt: new Date().toISOString(),
@@ -2936,7 +2997,7 @@ function rollbackImport(store, batchId) {
2936
2997
  items
2937
2998
  };
2938
2999
  }
2939
- function previewRecord(store, source, record, defaults) {
3000
+ function previewRecord(store, source, record, defaults, options) {
2940
3001
  const warnings = [];
2941
3002
  let candidate;
2942
3003
  try {
@@ -2956,13 +3017,16 @@ function previewRecord(store, source, record, defaults) {
2956
3017
  reason: error instanceof Error ? error.message : String(error)
2957
3018
  };
2958
3019
  }
2959
- const provenance = store.getProvenance(candidate.source, candidate.sourceId);
2960
- const monitor = provenance ? store.getMonitor(provenance.monitorId) : store.getMonitor(candidate.name);
2961
- if (provenance && !monitor) {
3020
+ const monitorOptions = options.workspaceId ? { workspaceId: options.workspaceId } : undefined;
3021
+ const rawProvenance = store.getProvenance(candidate.source, candidate.sourceId);
3022
+ const provenanceMonitor = rawProvenance ? store.getMonitor(rawProvenance.monitorId, monitorOptions) : null;
3023
+ const provenance = provenanceMonitor ? rawProvenance : null;
3024
+ const monitor = provenanceMonitor ?? store.getMonitor(candidate.name, monitorOptions);
3025
+ if (rawProvenance && !provenanceMonitor && !options.workspaceId) {
2962
3026
  return { candidate, action: "create", monitor: null, provenance, warnings: ["source provenance points to a missing monitor"], reason: null };
2963
3027
  }
2964
3028
  if (provenance && monitor) {
2965
- const nameOwner = store.getMonitor(candidate.name);
3029
+ const nameOwner = store.getMonitor(candidate.name, monitorOptions);
2966
3030
  if (nameOwner && nameOwner.id !== monitor.id) {
2967
3031
  return {
2968
3032
  candidate,
@@ -5310,8 +5374,8 @@ class UptimeService {
5310
5374
  const execute = () => this.submitProbeResultInTransaction(input);
5311
5375
  return this.store.runInTransaction ? this.store.runInTransaction(execute) : execute();
5312
5376
  }
5313
- previewImport(request) {
5314
- return previewImport(this.store, request);
5377
+ previewImport(request, options = {}) {
5378
+ return previewImport(this.store, request, options);
5315
5379
  }
5316
5380
  applyImport(request) {
5317
5381
  return applyImport(this.store, request);
@@ -6355,7 +6419,7 @@ async function handleApiRoute(service, request, url, apiPath, options, hosted, a
6355
6419
  return json(service.submitProbeResult(await jsonBody(request)), 201);
6356
6420
  }
6357
6421
  if (request.method === "POST" && apiPath === "/api/imports/preview") {
6358
- return json(service.previewImport(await jsonBody(request)));
6422
+ return json(service.previewImport(await jsonBody(request), { workspaceId: actor?.workspaceId }));
6359
6423
  }
6360
6424
  if (request.method === "POST" && apiPath === "/api/imports/apply") {
6361
6425
  if (hosted)
@@ -6535,7 +6599,7 @@ function buildAwsDeploymentPlan(options = {}) {
6535
6599
  const image = clean(options.image, `${imageRepositoryUri}@sha256:<image-digest>`);
6536
6600
  const evidenceBucket = clean(options.evidenceBucket, `hasna-${stage}-${prefix}-evidence`);
6537
6601
  const hostedSqliteDbPath = clean(options.hostedSqliteDbPath, DEFAULT_HOSTED_SQLITE_DB);
6538
- const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.10");
6602
+ const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.11");
6539
6603
  const protectedAccessMode = options.protectedAccessMode ?? DEFAULT_PROTECTED_ACCESS_MODE;
6540
6604
  const protectedAccessUrl = protectedAccessMode === "cloudfront_default_domain" ? "https://<cloudfront-domain>" : `https://${hostname}`;
6541
6605
  const cluster = `${prefix}-${stage}`;
@@ -21,7 +21,7 @@ function buildAwsDeploymentPlan(options = {}) {
21
21
  const image = clean(options.image, `${imageRepositoryUri}@sha256:<image-digest>`);
22
22
  const evidenceBucket = clean(options.evidenceBucket, `hasna-${stage}-${prefix}-evidence`);
23
23
  const hostedSqliteDbPath = clean(options.hostedSqliteDbPath, DEFAULT_HOSTED_SQLITE_DB);
24
- const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.10");
24
+ const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.11");
25
25
  const protectedAccessMode = options.protectedAccessMode ?? DEFAULT_PROTECTED_ACCESS_MODE;
26
26
  const protectedAccessUrl = protectedAccessMode === "cloudfront_default_domain" ? "https://<cloudfront-domain>" : `https://${hostname}`;
27
27
  const cluster = `${prefix}-${stage}`;
package/dist/imports.d.ts CHANGED
@@ -71,7 +71,9 @@ export interface UptimeImportStore {
71
71
  allowBrowserPage?: boolean;
72
72
  }): Monitor;
73
73
  deleteMonitor(idOrName: string): boolean;
74
- getMonitor(idOrName: string): Monitor | null;
74
+ getMonitor(idOrName: string, options?: {
75
+ workspaceId?: string;
76
+ }): Monitor | null;
75
77
  listResults(options?: ListResultsOptions): unknown[];
76
78
  getProvenance(source: string, sourceId: string): MonitorProvenance | null;
77
79
  upsertMonitorProvenance(input: UpsertMonitorProvenanceInput): MonitorProvenance;
@@ -84,7 +86,9 @@ export interface UptimeImportStore {
84
86
  markImportBatchRolledBack(batchId: string): StoredImportBatch;
85
87
  runInTransaction?<T>(fn: () => T): T;
86
88
  }
87
- export declare function previewImport(store: UptimeImportStore, request: ImportRequest): ImportPreview;
89
+ export declare function previewImport(store: UptimeImportStore, request: ImportRequest, options?: {
90
+ workspaceId?: string;
91
+ }): ImportPreview;
88
92
  export declare function applyImport(store: UptimeImportStore, request: ImportRequest): ImportApplyResult;
89
93
  export declare function rollbackImport(store: UptimeImportStore, batchId: string): ImportRollbackResult;
90
94
  //# sourceMappingURL=imports.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../src/imports.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../src/imports.ts"],"names":[],"mappings":"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"}
package/dist/imports.js CHANGED
@@ -81,13 +81,74 @@ function isDeniedIpv4(ip) {
81
81
  }
82
82
  function isDeniedIpv6(ip) {
83
83
  const normalized = ip.toLowerCase();
84
- return normalized === "::" || normalized === "::1" || normalized.startsWith("fe80:") || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("ff") || normalized.startsWith("::ffff:127.") || normalized.startsWith("::ffff:10.") || normalized.startsWith("::ffff:169.254.") || /^::ffff:172\.(1[6-9]|2\d|3[0-1])\./.test(normalized) || normalized.startsWith("::ffff:192.168.");
84
+ const mappedIpv4 = ipv4FromMappedIpv6(normalized);
85
+ if (mappedIpv4)
86
+ return isDeniedIpv4(mappedIpv4);
87
+ const words = parseIpv6Words(normalized);
88
+ return normalized === "::" || normalized === "::1" || words !== null && (words[0] & 65472) === 65152 || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("ff");
89
+ }
90
+ function ipv4FromMappedIpv6(ip) {
91
+ const words = parseIpv6Words(ip);
92
+ if (!words)
93
+ return null;
94
+ if (words[0] !== 0 || words[1] !== 0 || words[2] !== 0 || words[3] !== 0 || words[4] !== 0 || words[5] !== 65535) {
95
+ return null;
96
+ }
97
+ return [
98
+ words[6] >> 8,
99
+ words[6] & 255,
100
+ words[7] >> 8,
101
+ words[7] & 255
102
+ ].join(".");
103
+ }
104
+ function parseIpv6Words(value) {
105
+ let ip = value.toLowerCase();
106
+ const zoneIndex = ip.indexOf("%");
107
+ if (zoneIndex >= 0)
108
+ ip = ip.slice(0, zoneIndex);
109
+ if (ip.includes(".")) {
110
+ const lastColon = ip.lastIndexOf(":");
111
+ if (lastColon < 0)
112
+ return null;
113
+ const ipv4 = parseIpv4Words(ip.slice(lastColon + 1));
114
+ if (!ipv4)
115
+ return null;
116
+ ip = `${ip.slice(0, lastColon)}:${(ipv4[0] << 8 | ipv4[1]).toString(16)}:${(ipv4[2] << 8 | ipv4[3]).toString(16)}`;
117
+ }
118
+ const compressed = ip.split("::");
119
+ if (compressed.length > 2)
120
+ return null;
121
+ const left = parseIpv6Side(compressed[0]);
122
+ const right = compressed.length === 2 ? parseIpv6Side(compressed[1]) : [];
123
+ if (!left || !right)
124
+ return null;
125
+ if (compressed.length === 1)
126
+ return left.length === 8 ? left : null;
127
+ const missing = 8 - left.length - right.length;
128
+ if (missing < 1)
129
+ return null;
130
+ return [...left, ...Array(missing).fill(0), ...right];
131
+ }
132
+ function parseIpv6Side(value) {
133
+ if (!value)
134
+ return [];
135
+ const words = value.split(":");
136
+ if (words.some((word) => !/^[0-9a-f]{1,4}$/.test(word)))
137
+ return null;
138
+ return words.map((word) => Number.parseInt(word, 16));
139
+ }
140
+ function parseIpv4Words(value) {
141
+ const words = value.split(".").map((part) => Number(part));
142
+ if (words.length !== 4 || words.some((word) => !Number.isInteger(word) || word < 0 || word > 255)) {
143
+ return null;
144
+ }
145
+ return words;
85
146
  }
86
147
 
87
148
  // src/imports.ts
88
- function previewImport(store, request) {
149
+ function previewImport(store, request, options = {}) {
89
150
  const source = normalizeSource(request.source);
90
- const items = dedupePreviewItems(request.records.map((record) => previewRecord(store, source, record, request.defaults ?? {})));
151
+ const items = dedupePreviewItems(request.records.map((record) => previewRecord(store, source, record, request.defaults ?? {}, options)));
91
152
  return {
92
153
  source,
93
154
  generatedAt: new Date().toISOString(),
@@ -161,7 +222,7 @@ function rollbackImport(store, batchId) {
161
222
  items
162
223
  };
163
224
  }
164
- function previewRecord(store, source, record, defaults) {
225
+ function previewRecord(store, source, record, defaults, options) {
165
226
  const warnings = [];
166
227
  let candidate;
167
228
  try {
@@ -181,13 +242,16 @@ function previewRecord(store, source, record, defaults) {
181
242
  reason: error instanceof Error ? error.message : String(error)
182
243
  };
183
244
  }
184
- const provenance = store.getProvenance(candidate.source, candidate.sourceId);
185
- const monitor = provenance ? store.getMonitor(provenance.monitorId) : store.getMonitor(candidate.name);
186
- if (provenance && !monitor) {
245
+ const monitorOptions = options.workspaceId ? { workspaceId: options.workspaceId } : undefined;
246
+ const rawProvenance = store.getProvenance(candidate.source, candidate.sourceId);
247
+ const provenanceMonitor = rawProvenance ? store.getMonitor(rawProvenance.monitorId, monitorOptions) : null;
248
+ const provenance = provenanceMonitor ? rawProvenance : null;
249
+ const monitor = provenanceMonitor ?? store.getMonitor(candidate.name, monitorOptions);
250
+ if (rawProvenance && !provenanceMonitor && !options.workspaceId) {
187
251
  return { candidate, action: "create", monitor: null, provenance, warnings: ["source provenance points to a missing monitor"], reason: null };
188
252
  }
189
253
  if (provenance && monitor) {
190
- const nameOwner = store.getMonitor(candidate.name);
254
+ const nameOwner = store.getMonitor(candidate.name, monitorOptions);
191
255
  if (nameOwner && nameOwner.id !== monitor.id) {
192
256
  return {
193
257
  candidate,
package/dist/index.js CHANGED
@@ -276,13 +276,74 @@ function isDeniedIpv4(ip) {
276
276
  }
277
277
  function isDeniedIpv6(ip) {
278
278
  const normalized = ip.toLowerCase();
279
- return normalized === "::" || normalized === "::1" || normalized.startsWith("fe80:") || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("ff") || normalized.startsWith("::ffff:127.") || normalized.startsWith("::ffff:10.") || normalized.startsWith("::ffff:169.254.") || /^::ffff:172\.(1[6-9]|2\d|3[0-1])\./.test(normalized) || normalized.startsWith("::ffff:192.168.");
279
+ const mappedIpv4 = ipv4FromMappedIpv6(normalized);
280
+ if (mappedIpv4)
281
+ return isDeniedIpv4(mappedIpv4);
282
+ const words = parseIpv6Words(normalized);
283
+ return normalized === "::" || normalized === "::1" || words !== null && (words[0] & 65472) === 65152 || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("ff");
284
+ }
285
+ function ipv4FromMappedIpv6(ip) {
286
+ const words = parseIpv6Words(ip);
287
+ if (!words)
288
+ return null;
289
+ if (words[0] !== 0 || words[1] !== 0 || words[2] !== 0 || words[3] !== 0 || words[4] !== 0 || words[5] !== 65535) {
290
+ return null;
291
+ }
292
+ return [
293
+ words[6] >> 8,
294
+ words[6] & 255,
295
+ words[7] >> 8,
296
+ words[7] & 255
297
+ ].join(".");
298
+ }
299
+ function parseIpv6Words(value) {
300
+ let ip = value.toLowerCase();
301
+ const zoneIndex = ip.indexOf("%");
302
+ if (zoneIndex >= 0)
303
+ ip = ip.slice(0, zoneIndex);
304
+ if (ip.includes(".")) {
305
+ const lastColon = ip.lastIndexOf(":");
306
+ if (lastColon < 0)
307
+ return null;
308
+ const ipv4 = parseIpv4Words(ip.slice(lastColon + 1));
309
+ if (!ipv4)
310
+ return null;
311
+ ip = `${ip.slice(0, lastColon)}:${(ipv4[0] << 8 | ipv4[1]).toString(16)}:${(ipv4[2] << 8 | ipv4[3]).toString(16)}`;
312
+ }
313
+ const compressed = ip.split("::");
314
+ if (compressed.length > 2)
315
+ return null;
316
+ const left = parseIpv6Side(compressed[0]);
317
+ const right = compressed.length === 2 ? parseIpv6Side(compressed[1]) : [];
318
+ if (!left || !right)
319
+ return null;
320
+ if (compressed.length === 1)
321
+ return left.length === 8 ? left : null;
322
+ const missing = 8 - left.length - right.length;
323
+ if (missing < 1)
324
+ return null;
325
+ return [...left, ...Array(missing).fill(0), ...right];
326
+ }
327
+ function parseIpv6Side(value) {
328
+ if (!value)
329
+ return [];
330
+ const words = value.split(":");
331
+ if (words.some((word) => !/^[0-9a-f]{1,4}$/.test(word)))
332
+ return null;
333
+ return words.map((word) => Number.parseInt(word, 16));
334
+ }
335
+ function parseIpv4Words(value) {
336
+ const words = value.split(".").map((part) => Number(part));
337
+ if (words.length !== 4 || words.some((word) => !Number.isInteger(word) || word < 0 || word > 255)) {
338
+ return null;
339
+ }
340
+ return words;
280
341
  }
281
342
 
282
343
  // src/imports.ts
283
- function previewImport(store, request) {
344
+ function previewImport(store, request, options = {}) {
284
345
  const source = normalizeSource(request.source);
285
- const items = dedupePreviewItems(request.records.map((record) => previewRecord(store, source, record, request.defaults ?? {})));
346
+ const items = dedupePreviewItems(request.records.map((record) => previewRecord(store, source, record, request.defaults ?? {}, options)));
286
347
  return {
287
348
  source,
288
349
  generatedAt: new Date().toISOString(),
@@ -356,7 +417,7 @@ function rollbackImport(store, batchId) {
356
417
  items
357
418
  };
358
419
  }
359
- function previewRecord(store, source, record, defaults) {
420
+ function previewRecord(store, source, record, defaults, options) {
360
421
  const warnings = [];
361
422
  let candidate;
362
423
  try {
@@ -376,13 +437,16 @@ function previewRecord(store, source, record, defaults) {
376
437
  reason: error instanceof Error ? error.message : String(error)
377
438
  };
378
439
  }
379
- const provenance = store.getProvenance(candidate.source, candidate.sourceId);
380
- const monitor = provenance ? store.getMonitor(provenance.monitorId) : store.getMonitor(candidate.name);
381
- if (provenance && !monitor) {
440
+ const monitorOptions = options.workspaceId ? { workspaceId: options.workspaceId } : undefined;
441
+ const rawProvenance = store.getProvenance(candidate.source, candidate.sourceId);
442
+ const provenanceMonitor = rawProvenance ? store.getMonitor(rawProvenance.monitorId, monitorOptions) : null;
443
+ const provenance = provenanceMonitor ? rawProvenance : null;
444
+ const monitor = provenanceMonitor ?? store.getMonitor(candidate.name, monitorOptions);
445
+ if (rawProvenance && !provenanceMonitor && !options.workspaceId) {
382
446
  return { candidate, action: "create", monitor: null, provenance, warnings: ["source provenance points to a missing monitor"], reason: null };
383
447
  }
384
448
  if (provenance && monitor) {
385
- const nameOwner = store.getMonitor(candidate.name);
449
+ const nameOwner = store.getMonitor(candidate.name, monitorOptions);
386
450
  if (nameOwner && nameOwner.id !== monitor.id) {
387
451
  return {
388
452
  candidate,
@@ -2729,8 +2793,8 @@ class UptimeService {
2729
2793
  const execute = () => this.submitProbeResultInTransaction(input);
2730
2794
  return this.store.runInTransaction ? this.store.runInTransaction(execute) : execute();
2731
2795
  }
2732
- previewImport(request) {
2733
- return previewImport(this.store, request);
2796
+ previewImport(request, options = {}) {
2797
+ return previewImport(this.store, request, options);
2734
2798
  }
2735
2799
  applyImport(request) {
2736
2800
  return applyImport(this.store, request);
@@ -3758,7 +3822,7 @@ async function handleApiRoute(service, request, url, apiPath, options, hosted, a
3758
3822
  return json(service.submitProbeResult(await jsonBody(request)), 201);
3759
3823
  }
3760
3824
  if (request.method === "POST" && apiPath === "/api/imports/preview") {
3761
- return json(service.previewImport(await jsonBody(request)));
3825
+ return json(service.previewImport(await jsonBody(request), { workspaceId: actor?.workspaceId }));
3762
3826
  }
3763
3827
  if (request.method === "POST" && apiPath === "/api/imports/apply") {
3764
3828
  if (hosted)
@@ -3938,7 +4002,7 @@ function buildAwsDeploymentPlan(options = {}) {
3938
4002
  const image = clean(options.image, `${imageRepositoryUri}@sha256:<image-digest>`);
3939
4003
  const evidenceBucket = clean(options.evidenceBucket, `hasna-${stage}-${prefix}-evidence`);
3940
4004
  const hostedSqliteDbPath = clean(options.hostedSqliteDbPath, DEFAULT_HOSTED_SQLITE_DB);
3941
- const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.10");
4005
+ const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.11");
3942
4006
  const protectedAccessMode = options.protectedAccessMode ?? DEFAULT_PROTECTED_ACCESS_MODE;
3943
4007
  const protectedAccessUrl = protectedAccessMode === "cloudfront_default_domain" ? "https://<cloudfront-domain>" : `https://${hostname}`;
3944
4008
  const cluster = `${prefix}-${stage}`;