@intenque/network-sdk 2.2.0-beta.1 → 2.2.0-beta.2

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
@@ -3,7 +3,56 @@
3
3
  `@intenque/network-sdk`. The SDK major is the manifest schema major (spec §6.1). Design decisions are in the hub
4
4
  repository: `docs/Network_CrossPromo_Plan.md` and `docs/spec-addendum-phase0-auth.md` ("# Phase 3: conversions").
5
5
 
6
- ## 2.2.0-beta.1 (in progress)
6
+ ## 2.2.0-beta.2
7
+
8
+ Security fix round (hub Phase 3 WP19). No API change; one new drop reason.
9
+
10
+ ### Server entry (`@intenque/network-sdk/server`)
11
+
12
+ - **Misrouted answers are retried, not dropped.** `403`, `404` and any 3xx the classifier sees (`300`, `304`, or a
13
+ `fetch` that hands redirects back) were terminal after one attempt, so a missing route prefix, a WAF or a
14
+ maintenance page in front of the hub lost every report in that window. They are now `misrouted`
15
+ (`classifyResponse` returns the new kind): retried in process after 2, 8 and 30 s, then every 10 min from the
16
+ outbox, and dropped only 24 h after the first 401 / missing-config / misrouted answer with reason `misrouted_24h`
17
+ (the clock is the existing `unauthorizedSince` field, shared with the 401 rule; no outbox migration). A real
18
+ redirect (`301`/`302`/`303`/`307`/`308`, with or without `Location`) is still a transport error under
19
+ `redirect: 'error'` (status 0, retryable), as before. `400`, other `409`s, `402`, `413`, `422` and the rest of 4xx
20
+ stay terminal.
21
+ - **The responder's error code is bounded.** A code is kept (logged in `inr_outbox_dropped_total` /
22
+ `INR_UNAUTHORIZED`, stored in `lastError.code`) only when it matches `^[A-Z][A-Z0-9_]{0,63}$`; anything else
23
+ (a captive portal, a misrouted proxy, a 5000-character string) is `null`.
24
+ - **`referralMiddleware()` strips `inrRef` from any object body** (arrays and class instances too, not only plain
25
+ objects) with `Reflect.deleteProperty`, and never throws: a frozen body or a proxy that refuses keeps the field,
26
+ a `null` or primitive body is left alone. `req.inrRef` is set as before.
27
+ - **`forget()` stops a running send chain.** Before each in-process retry the chain checks that its outbox row (or
28
+ spill entry) still exists; once `forget(clickId)` purged it, no further attempt is sent. An outbox that errors or
29
+ times out on that check keeps the chain sending (as before).
30
+ - **`forget()` during a drain tick's spill move.** The drainer moved a snapshot of the in-memory spill, so a spilled
31
+ report that `forget(clickId)` removed while an earlier entry was being written was inserted into the outbox after
32
+ the purge (and `forget()` had returned `true`); the next tick sent the erased click id again. The move now skips an
33
+ entry no longer spilled, and removes the row again when the entry left the spill while its own write was in flight
34
+ (forget, or a send that finished). A failed write still leaves the entry spilled for the next tick.
35
+
36
+ ### Verifier (`@intenque/network-sdk/react`, the manifest verifier)
37
+
38
+ - **Destination URLs: RFC 3986 characters only after the authority** (unreserved, sub-delims, `:`, `@`, `/`, `?`,
39
+ `%XX`, one `#`). The hub now stores business URLs with `^`, `|`, a backtick, `{`, `}`, `[`, `]` and non-ASCII
40
+ percent-encoded and refuses
41
+ to sign anything else, so the SDK and the hub agree on every URL in every engine (a raw `^` or `|` used to pass
42
+ the hub and fail the SDK in browsers, taking the whole manifest down).
43
+ - **Signer dates must be their own `toISOString()`** (`2027-02-30`, `T24:00`, second 60 are `malformed`), the same
44
+ rule as the hub's verifier: engines disagree on those dates.
45
+ - **A root key that is not exactly `crv: 'P-256'` never verifies** (Firefox's WebCrypto accepts `p-256`).
46
+ - **A root key must have the shape the hub requires of its own roots** (`PublicJwkSchema`: `kty: 'EC'`,
47
+ `crv: 'P-256'`, `x` / `y` canonical base64url of 32 bytes), else `root_sig`; other members are ignored, as
48
+ before. The SDK handed WebCrypto `kty: 'EC'` whatever the site's `manifestRootKeys` said, so `kty: 'ec'`, `'RSA'`
49
+ or none verified in every engine while the hub refuses such a key, and a padded `x` verified in WebKit only.
50
+ - **Hosts with right-to-left text** (hub side, no SDK change): the hub now refuses a business URL whose host breaks
51
+ the RFC 5893 bidi rule (e.g. `24.בדיקה.co.il`, `1shop.عرب.com`, `aא.com`, `٣.com`), which Firefox's URL parser
52
+ refuses, so a signed manifest can no longer be `malformed` in Firefox alone. The SDK schema does not repeat that
53
+ check (bundle size; the hub never signs such a host).
54
+
55
+ ## 2.2.0-beta.1
7
56
 
8
57
  ### Server entry (`@intenque/network-sdk/server`)
9
58
 
@@ -1 +1 @@
1
- "use strict";var u=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var C=(n,e)=>{for(var t in e)u(n,t,{get:e[t],enumerable:!0})},M=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of y(e))!k.call(n,r)&&r!==t&&u(n,r,{get:()=>e[r],enumerable:!(o=m(e,r))||o.enumerable});return n};var L=n=>M(u({},"__esModule",{value:!0}),n);var w={};C(w,{attachReferral:()=>A,attachReferralHeader:()=>O,captureReferral:()=>b,clearReferral:()=>N,getReferral:()=>I,onConsentChange:()=>T,setConsent:()=>E});module.exports=L(w);var _="2.2.0-beta.1";function S(n,e,t){let o=s=>{try{t?.(s)}catch{}return s};try{if(location.protocol=="https:"&&/^http:/i.test(n))return o(!1)}catch{}try{if(navigator.sendBeacon(n,e))return o(!0)}catch{}let r=s=>fetch(n,{method:"POST",mode:"no-cors",keepalive:s,credentials:"omit",body:e});try{return r(!0).then(()=>o(!0),()=>{try{if(document.visibilityState=="visible")return void r(!1).then(()=>o(!0),()=>o(!1))}catch{}o(!1)}),!0}catch{return o(!1)}}var f="inr_ref",h=2592e6,a={},l=null,c=(n,e)=>{try{let t=n?localStorage:sessionStorage;if(e===void 0)return JSON.parse(t.getItem(f)||"null");e?t.setItem(f,JSON.stringify(e)):t.removeItem(f)}catch{}return null},R=()=>!!navigator.globalPrivacyControl,p=()=>a.consent?.get()=="granted"&&!R(),g="",x=n=>{let e=a.endpoint,t=n.last.c;if(!e||!a.siteKey||g==t)return;g=t;let o=r=>{let s=Date.now();S(e.replace(/\/+$/,"")+"/landing",JSON.stringify({v:2,k:a.siteKey,c:t,scope:"session",ph:r&&[...new Uint8Array(r,0,8)].map(i=>(i+256).toString(16).slice(1)).join(""),sdk:_,t:s,d:Math.min(h,Math.max(0,s-n.last.t))}))};try{crypto.subtle.digest("SHA-256",new TextEncoder().encode(location.pathname)).then(o,()=>o())}catch{o()}};function b(n={}){a=n;try{let e=new URL(location.href),t=e.searchParams.get("inr");if(!t||(e.searchParams.delete("inr"),history.replaceState(history.state,"",e.href),!/^[A-Za-z0-9_-]{32}$/.test(t)))return;let o={c:t,t:Date.now(),from:e.searchParams.get("utm_source")},r=c(0),s={last:o,first:r?.first||o};if(n.consentMode=="require-consent"&&!p()){l=s;return}c(0,s),p()&&c(1,s),x(s)}catch{}}function E(n){try{if(n=="granted"){let e=l;if(l=null,e&&R())return;let t=e||c(0);t&&(c(0,t),p()&&c(1,t),e&&x(e))}else l=null,c(1,null)}catch{}}var T=E;function N(){l=null,c(0,null),c(1,null)}function I(){try{let n=c(0),e=c(1),t=n&&(!e||n.last.t>=e.last.t)?n:e;return!t||Date.now()-t.last.t>h?null:{clickId:t.last.c,firstClickId:t.first.c==t.last.c?null:t.first.c,capturedAt:t.last.t,tokenScope:n&&n.last.c==t.last.c?"session":"persistent",consented:!!p()}}catch{return null}}function A(n,e={}){n.interceptors.request.use(t=>{try{if(/^(get|head|options)?$/i.test(t.method||""))return t;let o=new URL((t.baseURL||n.defaults?.baseURL||"").replace(/\/*$/,"/"),location.origin),r=new URL((t.url||"").replace(/^\/(?!\/)/,""),o),s=r.pathname;if(r.origin!=o.origin&&!e.origins?.includes(r.origin)||e.routes&&!e.routes.some(d=>d.test?~s.search(d):d==s))return t;let i=I();if(!i)return t;e.mode=="header"?(t.headers=t.headers||{},t.headers["X-INR-Ref"]=i.clickId,t.headers["X-INR-Ref-Scope"]=i.tokenScope,i.firstClickId&&(t.headers["X-INR-Ref-First"]=i.firstClickId)):(t.data==null||Object.getPrototypeOf(t.data)==Object.prototype)&&(t.data={...t.data,inrRef:{clickId:i.clickId,firstClickId:i.firstClickId,tokenScope:i.tokenScope}})}catch{}return t})}var O=(n,e={})=>A(n,{...e,mode:"header"});
1
+ "use strict";var u=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var C=(n,e)=>{for(var t in e)u(n,t,{get:e[t],enumerable:!0})},M=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of y(e))!k.call(n,r)&&r!==t&&u(n,r,{get:()=>e[r],enumerable:!(o=m(e,r))||o.enumerable});return n};var L=n=>M(u({},"__esModule",{value:!0}),n);var w={};C(w,{attachReferral:()=>A,attachReferralHeader:()=>O,captureReferral:()=>b,clearReferral:()=>N,getReferral:()=>I,onConsentChange:()=>T,setConsent:()=>E});module.exports=L(w);var _="2.2.0-beta.2";function S(n,e,t){let o=s=>{try{t?.(s)}catch{}return s};try{if(location.protocol=="https:"&&/^http:/i.test(n))return o(!1)}catch{}try{if(navigator.sendBeacon(n,e))return o(!0)}catch{}let r=s=>fetch(n,{method:"POST",mode:"no-cors",keepalive:s,credentials:"omit",body:e});try{return r(!0).then(()=>o(!0),()=>{try{if(document.visibilityState=="visible")return void r(!1).then(()=>o(!0),()=>o(!1))}catch{}o(!1)}),!0}catch{return o(!1)}}var f="inr_ref",h=2592e6,a={},l=null,c=(n,e)=>{try{let t=n?localStorage:sessionStorage;if(e===void 0)return JSON.parse(t.getItem(f)||"null");e?t.setItem(f,JSON.stringify(e)):t.removeItem(f)}catch{}return null},R=()=>!!navigator.globalPrivacyControl,p=()=>a.consent?.get()=="granted"&&!R(),g="",x=n=>{let e=a.endpoint,t=n.last.c;if(!e||!a.siteKey||g==t)return;g=t;let o=r=>{let s=Date.now();S(e.replace(/\/+$/,"")+"/landing",JSON.stringify({v:2,k:a.siteKey,c:t,scope:"session",ph:r&&[...new Uint8Array(r,0,8)].map(i=>(i+256).toString(16).slice(1)).join(""),sdk:_,t:s,d:Math.min(h,Math.max(0,s-n.last.t))}))};try{crypto.subtle.digest("SHA-256",new TextEncoder().encode(location.pathname)).then(o,()=>o())}catch{o()}};function b(n={}){a=n;try{let e=new URL(location.href),t=e.searchParams.get("inr");if(!t||(e.searchParams.delete("inr"),history.replaceState(history.state,"",e.href),!/^[A-Za-z0-9_-]{32}$/.test(t)))return;let o={c:t,t:Date.now(),from:e.searchParams.get("utm_source")},r=c(0),s={last:o,first:r?.first||o};if(n.consentMode=="require-consent"&&!p()){l=s;return}c(0,s),p()&&c(1,s),x(s)}catch{}}function E(n){try{if(n=="granted"){let e=l;if(l=null,e&&R())return;let t=e||c(0);t&&(c(0,t),p()&&c(1,t),e&&x(e))}else l=null,c(1,null)}catch{}}var T=E;function N(){l=null,c(0,null),c(1,null)}function I(){try{let n=c(0),e=c(1),t=n&&(!e||n.last.t>=e.last.t)?n:e;return!t||Date.now()-t.last.t>h?null:{clickId:t.last.c,firstClickId:t.first.c==t.last.c?null:t.first.c,capturedAt:t.last.t,tokenScope:n&&n.last.c==t.last.c?"session":"persistent",consented:!!p()}}catch{return null}}function A(n,e={}){n.interceptors.request.use(t=>{try{if(/^(get|head|options)?$/i.test(t.method||""))return t;let o=new URL((t.baseURL||n.defaults?.baseURL||"").replace(/\/*$/,"/"),location.origin),r=new URL((t.url||"").replace(/^\/(?!\/)/,""),o),s=r.pathname;if(r.origin!=o.origin&&!e.origins?.includes(r.origin)||e.routes&&!e.routes.some(d=>d.test?~s.search(d):d==s))return t;let i=I();if(!i)return t;e.mode=="header"?(t.headers=t.headers||{},t.headers["X-INR-Ref"]=i.clickId,t.headers["X-INR-Ref-Scope"]=i.tokenScope,i.firstClickId&&(t.headers["X-INR-Ref-First"]=i.firstClickId)):(t.data==null||Object.getPrototypeOf(t.data)==Object.prototype)&&(t.data={...t.data,inrRef:{clickId:i.clickId,firstClickId:i.firstClickId,tokenScope:i.tokenScope}})}catch{}return t})}var O=(n,e={})=>A(n,{...e,mode:"header"});
@@ -1 +1 @@
1
- var f="2.2.0-beta.1";function _(n,e,t){let o=r=>{try{t?.(r)}catch{}return r};try{if(location.protocol=="https:"&&/^http:/i.test(n))return o(!1)}catch{}try{if(navigator.sendBeacon(n,e))return o(!0)}catch{}let s=r=>fetch(n,{method:"POST",mode:"no-cors",keepalive:r,credentials:"omit",body:e});try{return s(!0).then(()=>o(!0),()=>{try{if(document.visibilityState=="visible")return void s(!1).then(()=>o(!0),()=>o(!1))}catch{}o(!1)}),!0}catch{return o(!1)}}var u="inr_ref",g=2592e6,a={},l=null,c=(n,e)=>{try{let t=n?localStorage:sessionStorage;if(e===void 0)return JSON.parse(t.getItem(u)||"null");e?t.setItem(u,JSON.stringify(e)):t.removeItem(u)}catch{}return null},h=()=>!!navigator.globalPrivacyControl,p=()=>a.consent?.get()=="granted"&&!h(),S="",R=n=>{let e=a.endpoint,t=n.last.c;if(!e||!a.siteKey||S==t)return;S=t;let o=s=>{let r=Date.now();_(e.replace(/\/+$/,"")+"/landing",JSON.stringify({v:2,k:a.siteKey,c:t,scope:"session",ph:s&&[...new Uint8Array(s,0,8)].map(i=>(i+256).toString(16).slice(1)).join(""),sdk:f,t:r,d:Math.min(g,Math.max(0,r-n.last.t))}))};try{crypto.subtle.digest("SHA-256",new TextEncoder().encode(location.pathname)).then(o,()=>o())}catch{o()}};function C(n={}){a=n;try{let e=new URL(location.href),t=e.searchParams.get("inr");if(!t||(e.searchParams.delete("inr"),history.replaceState(history.state,"",e.href),!/^[A-Za-z0-9_-]{32}$/.test(t)))return;let o={c:t,t:Date.now(),from:e.searchParams.get("utm_source")},s=c(0),r={last:o,first:s?.first||o};if(n.consentMode=="require-consent"&&!p()){l=r;return}c(0,r),p()&&c(1,r),R(r)}catch{}}function x(n){try{if(n=="granted"){let e=l;if(l=null,e&&h())return;let t=e||c(0);t&&(c(0,t),p()&&c(1,t),e&&R(e))}else l=null,c(1,null)}catch{}}var M=x;function L(){l=null,c(0,null),c(1,null)}function E(){try{let n=c(0),e=c(1),t=n&&(!e||n.last.t>=e.last.t)?n:e;return!t||Date.now()-t.last.t>g?null:{clickId:t.last.c,firstClickId:t.first.c==t.last.c?null:t.first.c,capturedAt:t.last.t,tokenScope:n&&n.last.c==t.last.c?"session":"persistent",consented:!!p()}}catch{return null}}function I(n,e={}){n.interceptors.request.use(t=>{try{if(/^(get|head|options)?$/i.test(t.method||""))return t;let o=new URL((t.baseURL||n.defaults?.baseURL||"").replace(/\/*$/,"/"),location.origin),s=new URL((t.url||"").replace(/^\/(?!\/)/,""),o),r=s.pathname;if(s.origin!=o.origin&&!e.origins?.includes(s.origin)||e.routes&&!e.routes.some(d=>d.test?~r.search(d):d==r))return t;let i=E();if(!i)return t;e.mode=="header"?(t.headers=t.headers||{},t.headers["X-INR-Ref"]=i.clickId,t.headers["X-INR-Ref-Scope"]=i.tokenScope,i.firstClickId&&(t.headers["X-INR-Ref-First"]=i.firstClickId)):(t.data==null||Object.getPrototypeOf(t.data)==Object.prototype)&&(t.data={...t.data,inrRef:{clickId:i.clickId,firstClickId:i.firstClickId,tokenScope:i.tokenScope}})}catch{}return t})}var b=(n,e={})=>I(n,{...e,mode:"header"});export{I as attachReferral,b as attachReferralHeader,C as captureReferral,L as clearReferral,E as getReferral,M as onConsentChange,x as setConsent};
1
+ var f="2.2.0-beta.2";function _(n,e,t){let o=r=>{try{t?.(r)}catch{}return r};try{if(location.protocol=="https:"&&/^http:/i.test(n))return o(!1)}catch{}try{if(navigator.sendBeacon(n,e))return o(!0)}catch{}let s=r=>fetch(n,{method:"POST",mode:"no-cors",keepalive:r,credentials:"omit",body:e});try{return s(!0).then(()=>o(!0),()=>{try{if(document.visibilityState=="visible")return void s(!1).then(()=>o(!0),()=>o(!1))}catch{}o(!1)}),!0}catch{return o(!1)}}var u="inr_ref",g=2592e6,a={},l=null,c=(n,e)=>{try{let t=n?localStorage:sessionStorage;if(e===void 0)return JSON.parse(t.getItem(u)||"null");e?t.setItem(u,JSON.stringify(e)):t.removeItem(u)}catch{}return null},h=()=>!!navigator.globalPrivacyControl,p=()=>a.consent?.get()=="granted"&&!h(),S="",R=n=>{let e=a.endpoint,t=n.last.c;if(!e||!a.siteKey||S==t)return;S=t;let o=s=>{let r=Date.now();_(e.replace(/\/+$/,"")+"/landing",JSON.stringify({v:2,k:a.siteKey,c:t,scope:"session",ph:s&&[...new Uint8Array(s,0,8)].map(i=>(i+256).toString(16).slice(1)).join(""),sdk:f,t:r,d:Math.min(g,Math.max(0,r-n.last.t))}))};try{crypto.subtle.digest("SHA-256",new TextEncoder().encode(location.pathname)).then(o,()=>o())}catch{o()}};function C(n={}){a=n;try{let e=new URL(location.href),t=e.searchParams.get("inr");if(!t||(e.searchParams.delete("inr"),history.replaceState(history.state,"",e.href),!/^[A-Za-z0-9_-]{32}$/.test(t)))return;let o={c:t,t:Date.now(),from:e.searchParams.get("utm_source")},s=c(0),r={last:o,first:s?.first||o};if(n.consentMode=="require-consent"&&!p()){l=r;return}c(0,r),p()&&c(1,r),R(r)}catch{}}function x(n){try{if(n=="granted"){let e=l;if(l=null,e&&h())return;let t=e||c(0);t&&(c(0,t),p()&&c(1,t),e&&R(e))}else l=null,c(1,null)}catch{}}var M=x;function L(){l=null,c(0,null),c(1,null)}function E(){try{let n=c(0),e=c(1),t=n&&(!e||n.last.t>=e.last.t)?n:e;return!t||Date.now()-t.last.t>g?null:{clickId:t.last.c,firstClickId:t.first.c==t.last.c?null:t.first.c,capturedAt:t.last.t,tokenScope:n&&n.last.c==t.last.c?"session":"persistent",consented:!!p()}}catch{return null}}function I(n,e={}){n.interceptors.request.use(t=>{try{if(/^(get|head|options)?$/i.test(t.method||""))return t;let o=new URL((t.baseURL||n.defaults?.baseURL||"").replace(/\/*$/,"/"),location.origin),s=new URL((t.url||"").replace(/^\/(?!\/)/,""),o),r=s.pathname;if(s.origin!=o.origin&&!e.origins?.includes(s.origin)||e.routes&&!e.routes.some(d=>d.test?~r.search(d):d==r))return t;let i=E();if(!i)return t;e.mode=="header"?(t.headers=t.headers||{},t.headers["X-INR-Ref"]=i.clickId,t.headers["X-INR-Ref-Scope"]=i.tokenScope,i.firstClickId&&(t.headers["X-INR-Ref-First"]=i.firstClickId)):(t.data==null||Object.getPrototypeOf(t.data)==Object.prototype)&&(t.data={...t.data,inrRef:{clickId:i.clickId,firstClickId:i.firstClickId,tokenScope:i.tokenScope}})}catch{}return t})}var b=(n,e={})=>I(n,{...e,mode:"header"});export{I as attachReferral,b as attachReferralHeader,C as captureReferral,L as clearReferral,E as getReferral,M as onConsentChange,x as setConsent};
package/dist/cli/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { webcrypto } from "crypto";
5
5
 
6
6
  // src/constants.ts
7
- var SDK_VERSION = "2.2.0-beta.1";
7
+ var SDK_VERSION = "2.2.0-beta.2";
8
8
  var MANIFEST_SCHEMA_MAJOR = 2;
9
9
  var DEFAULT_CDN_BASE = "https://inr-network-public.nyc3.cdn.digitaloceanspaces.com/network/v1";
10
10
  var MANIFEST_MAX_BYTES = 24 * 1024;
@@ -141,6 +141,7 @@ var COORD_RE = /^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
141
141
  var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
142
142
  var TOKEN_RE = /^[a-z0-9-]+\.\d+\.\d{8}\.[0-9a-f]{12}$/;
143
143
  var HOST_RE = /^[a-z0-9.-]+$/;
144
+ var URL_TAIL_RE = /^([\w.~!$&'()*+,;=:@/?-]|%[\da-f]{2})*(#([\w.~!$&'()*+,;=:@/?-]|%[\da-f]{2})*)?$/i;
144
145
  var Invalid = class extends Error {
145
146
  };
146
147
  var fail = () => {
@@ -220,7 +221,7 @@ function destinationUrlProblem(v) {
220
221
  if (host.startsWith("[") || /^\d+(\.\d+){3}$/.test(host)) return "IP addresses are not allowed";
221
222
  if (!HOST_RE.test(host) || !host.includes(".") || host.startsWith(".") || host.endsWith("."))
222
223
  return "invalid hostname";
223
- if (u.toString() !== v) return "must be normalised";
224
+ if (u.toString() !== v || !URL_TAIL_RE.test(v.slice(u.origin.length))) return "must be normalised";
224
225
  return null;
225
226
  }
226
227
  function destinationUrl(v) {
@@ -470,9 +471,10 @@ var keyCache = /* @__PURE__ */ new WeakMap();
470
471
  async function importKey(jwk, api) {
471
472
  const hit = keyCache.get(jwk);
472
473
  if (hit) return hit;
474
+ const { kty, crv, x, y } = jwk;
473
475
  const p = api.importKey(
474
476
  "jwk",
475
- { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y, ext: true },
477
+ { ...parsePublicJwk({ kty, crv, x, y }), ext: true },
476
478
  { name: "ECDSA", namedCurve: "P-256" },
477
479
  false,
478
480
  ["verify"]
@@ -514,7 +516,9 @@ function signedPayload(m) {
514
516
  }
515
517
  var isKidList = (v) => Array.isArray(v) && v.every((k) => typeof k === "string");
516
518
  function parseIso(s) {
517
- return Date.parse(s);
519
+ const t = Date.parse(s);
520
+ if (!new Date(t).toISOString().startsWith(s.slice(0, 19))) fail();
521
+ return t;
518
522
  }
519
523
  async function verifyManifest(input, opts) {
520
524
  try {
@@ -541,8 +545,7 @@ async function verifyManifest(input, opts) {
541
545
  return { ok: false, reason: "root_sig" };
542
546
  const notBefore = parseIso(s.notBefore);
543
547
  const notAfter = parseIso(s.notAfter);
544
- if (!Number.isFinite(notBefore) || !Number.isFinite(notAfter) || notAfter < notBefore)
545
- return { ok: false, reason: "malformed" };
548
+ if (notAfter < notBefore) return { ok: false, reason: "malformed" };
546
549
  if (opts.source === "live" && (t < notBefore || t > notAfter + SIGNER_GRACE_MS))
547
550
  return { ok: false, reason: "window" };
548
551
  if (m.sig.kid !== s.kid) return { ok: false, reason: "kid_mismatch" };