@trackunit/iris-app-runtime-core 1.17.31-alpha-84ab66282fc.0 → 1.17.31

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/index.cjs.js CHANGED
@@ -3,6 +3,107 @@
3
3
  var irisAppRuntimeCoreApi = require('@trackunit/iris-app-runtime-core-api');
4
4
  var penpal = require('penpal');
5
5
 
6
+ /**
7
+ * Trust anchor for the child (Iris app) side of the penpal bridge.
8
+ *
9
+ * An Iris app runs inside an iframe whose parent is the host (manager) page.
10
+ * Before this module the child connected with `allowedOrigins: ["*"]`, so any
11
+ * page could embed an app and drive it through the bridge. We pin the
12
+ * handshake to a *static whitelist* of Trackunit-controlled host domains — the
13
+ * whitelist, not the attacker-influenceable `document.referrer`, is the trust
14
+ * anchor.
15
+ *
16
+ * `document.referrer` is only used to *decide whether* we are confident enough
17
+ * to enforce the whitelist: the iframe sets `<meta name="referrer"
18
+ * content="strict-origin">`, so the referrer carries the embedding host's
19
+ * origin. When that origin resolves to a whitelisted Trackunit host we pin
20
+ * `allowedOrigins` to the whitelist; in every other case (non-whitelisted,
21
+ * blank, or unreadable referrer) we fail closed with `[]` so no parent can
22
+ * complete the handshake. There is deliberately no `["*"]` fallback — an
23
+ * embedder could blank or spoof its own referrer, so a wildcard fallback would
24
+ * re-open the exact unpinned path this module exists to close.
25
+ *
26
+ * The branded-domain list mirrors `BrandedUrls` in
27
+ * `libs/iris-app-sdk/iris-app-api/src/types/irisAppCspInput.ts`. It is
28
+ * duplicated here (as origin-matching RegExps rather than CSP `https://*.x`
29
+ * patterns) because `iris-app-runtime-core` does not depend on `iris-app-api`
30
+ * and adding that dependency for a static list isn't warranted. Keep the two
31
+ * lists in sync.
32
+ */
33
+ const BRANDED_HOST_DOMAINS = [
34
+ "trackunit.com",
35
+ "wackerneuson.com",
36
+ "manitou.com",
37
+ "niftylinkmanager.com",
38
+ "skyjack.com",
39
+ "ahernaccess.com",
40
+ "magnith.com",
41
+ "terberg.com",
42
+ "mymecalac.com",
43
+ "delille.be",
44
+ ];
45
+ const escapeForRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
46
+ /**
47
+ * Matches `https://<domain>` and `https://<any-subdomain>.<domain>` (the
48
+ * leading subdomain segments only contain DNS-safe characters, so a look-alike
49
+ * like `https://trackunit.com.evil.com` does not match).
50
+ *
51
+ * Note: unlike the CSP `https://*.<domain>` form this mirrors, the `(…)*` makes
52
+ * the subdomain optional, so the apex (`https://trackunit.com`) also matches.
53
+ * That is intentional — the apex is Trackunit-controlled — so don't "fix" it to
54
+ * require a subdomain to match the CSP shape.
55
+ */
56
+ const brandedHostPattern = (domain) => new RegExp(`^https:\\/\\/([a-z0-9-]+\\.)*${escapeForRegExp(domain)}$`);
57
+ /**
58
+ * The static whitelist of trusted host origins, suitable for direct use as
59
+ * penpal `WindowMessenger.allowedOrigins` (which accepts `Array<string |
60
+ * RegExp>` and tests each incoming message origin against the list).
61
+ */
62
+ const trustedHostOrigins = [
63
+ ...BRANDED_HOST_DOMAINS.map(brandedHostPattern),
64
+ // Dev / feature-branch host (e.g. `dev.iris.trackunit.app`).
65
+ /^https:\/\/([a-z0-9-]+\.)*trackunit\.app$/,
66
+ // Local development host.
67
+ /^http:\/\/localhost(:\d+)?$/,
68
+ ];
69
+ /**
70
+ * Whether the given window origin belongs to a trusted Trackunit host.
71
+ */
72
+ const isWhitelistedHostOrigin = (origin) => trustedHostOrigins.some(entry => (typeof entry === "string" ? entry === origin : entry.test(origin)));
73
+ // Capture-once cache. The iframe never switches domain, so the embedding host
74
+ // origin is resolved on first read and reused for the lifetime of the document
75
+ // (it is intentionally not recomputed after a `location.replace`).
76
+ let cachedHostOrigin;
77
+ /**
78
+ * Resolves the embedding host's origin from `document.referrer`, returning it
79
+ * only when it is whitelisted; otherwise `undefined` (non-whitelisted, blank,
80
+ * or unreadable referrer). The value is captured on the first call and
81
+ * memoized for the lifetime of the document.
82
+ */
83
+ const getTrustedHostOrigin = () => {
84
+ if (cachedHostOrigin) {
85
+ return cachedHostOrigin.value;
86
+ }
87
+ let value;
88
+ try {
89
+ const referrer = document.referrer;
90
+ const origin = referrer ? new URL(referrer).origin : undefined;
91
+ value = origin && isWhitelistedHostOrigin(origin) ? origin : undefined;
92
+ }
93
+ catch {
94
+ value = undefined;
95
+ }
96
+ cachedHostOrigin = { value };
97
+ return cachedHostOrigin.value;
98
+ };
99
+ /**
100
+ * The `allowedOrigins` to use for the child→host penpal handshake: the trusted
101
+ * whitelist when the embedding host origin resolves to a whitelisted domain,
102
+ * otherwise `[]` — fail closed. There is no `["*"]` fallback, so a parent that
103
+ * isn't a whitelisted Trackunit host can never complete the handshake.
104
+ */
105
+ const getHostConnectorAllowedOrigins = () => getTrustedHostOrigin() ? trustedHostOrigins : [];
106
+
6
107
  const doNothingByDefault = () => {
7
108
  // do nothing by default
8
109
  };
@@ -95,7 +196,10 @@ const setupHostConnector = (subscribedMethods, channel) => {
95
196
  ...subscribedMethods,
96
197
  };
97
198
  const connection = penpal.connect({
98
- messenger: new penpal.WindowMessenger({ remoteWindow: window.parent, allowedOrigins: ["*"] }),
199
+ // Pin the handshake to trusted Trackunit host origins. Degrades to ["*"]
200
+ // when the embedding host origin can't be resolved (older SDKs / unknown
201
+ // referrer) — see `trustedHostOrigin.ts`.
202
+ messenger: new penpal.WindowMessenger({ remoteWindow: window.parent, allowedOrigins: getHostConnectorAllowedOrigins() }),
99
203
  methods,
100
204
  channel,
101
205
  timeout: 10000,
package/index.esm.js CHANGED
@@ -1,6 +1,107 @@
1
1
  export * from '@trackunit/iris-app-runtime-core-api';
2
2
  import { connect, WindowMessenger } from 'penpal';
3
3
 
4
+ /**
5
+ * Trust anchor for the child (Iris app) side of the penpal bridge.
6
+ *
7
+ * An Iris app runs inside an iframe whose parent is the host (manager) page.
8
+ * Before this module the child connected with `allowedOrigins: ["*"]`, so any
9
+ * page could embed an app and drive it through the bridge. We pin the
10
+ * handshake to a *static whitelist* of Trackunit-controlled host domains — the
11
+ * whitelist, not the attacker-influenceable `document.referrer`, is the trust
12
+ * anchor.
13
+ *
14
+ * `document.referrer` is only used to *decide whether* we are confident enough
15
+ * to enforce the whitelist: the iframe sets `<meta name="referrer"
16
+ * content="strict-origin">`, so the referrer carries the embedding host's
17
+ * origin. When that origin resolves to a whitelisted Trackunit host we pin
18
+ * `allowedOrigins` to the whitelist; in every other case (non-whitelisted,
19
+ * blank, or unreadable referrer) we fail closed with `[]` so no parent can
20
+ * complete the handshake. There is deliberately no `["*"]` fallback — an
21
+ * embedder could blank or spoof its own referrer, so a wildcard fallback would
22
+ * re-open the exact unpinned path this module exists to close.
23
+ *
24
+ * The branded-domain list mirrors `BrandedUrls` in
25
+ * `libs/iris-app-sdk/iris-app-api/src/types/irisAppCspInput.ts`. It is
26
+ * duplicated here (as origin-matching RegExps rather than CSP `https://*.x`
27
+ * patterns) because `iris-app-runtime-core` does not depend on `iris-app-api`
28
+ * and adding that dependency for a static list isn't warranted. Keep the two
29
+ * lists in sync.
30
+ */
31
+ const BRANDED_HOST_DOMAINS = [
32
+ "trackunit.com",
33
+ "wackerneuson.com",
34
+ "manitou.com",
35
+ "niftylinkmanager.com",
36
+ "skyjack.com",
37
+ "ahernaccess.com",
38
+ "magnith.com",
39
+ "terberg.com",
40
+ "mymecalac.com",
41
+ "delille.be",
42
+ ];
43
+ const escapeForRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
44
+ /**
45
+ * Matches `https://<domain>` and `https://<any-subdomain>.<domain>` (the
46
+ * leading subdomain segments only contain DNS-safe characters, so a look-alike
47
+ * like `https://trackunit.com.evil.com` does not match).
48
+ *
49
+ * Note: unlike the CSP `https://*.<domain>` form this mirrors, the `(…)*` makes
50
+ * the subdomain optional, so the apex (`https://trackunit.com`) also matches.
51
+ * That is intentional — the apex is Trackunit-controlled — so don't "fix" it to
52
+ * require a subdomain to match the CSP shape.
53
+ */
54
+ const brandedHostPattern = (domain) => new RegExp(`^https:\\/\\/([a-z0-9-]+\\.)*${escapeForRegExp(domain)}$`);
55
+ /**
56
+ * The static whitelist of trusted host origins, suitable for direct use as
57
+ * penpal `WindowMessenger.allowedOrigins` (which accepts `Array<string |
58
+ * RegExp>` and tests each incoming message origin against the list).
59
+ */
60
+ const trustedHostOrigins = [
61
+ ...BRANDED_HOST_DOMAINS.map(brandedHostPattern),
62
+ // Dev / feature-branch host (e.g. `dev.iris.trackunit.app`).
63
+ /^https:\/\/([a-z0-9-]+\.)*trackunit\.app$/,
64
+ // Local development host.
65
+ /^http:\/\/localhost(:\d+)?$/,
66
+ ];
67
+ /**
68
+ * Whether the given window origin belongs to a trusted Trackunit host.
69
+ */
70
+ const isWhitelistedHostOrigin = (origin) => trustedHostOrigins.some(entry => (typeof entry === "string" ? entry === origin : entry.test(origin)));
71
+ // Capture-once cache. The iframe never switches domain, so the embedding host
72
+ // origin is resolved on first read and reused for the lifetime of the document
73
+ // (it is intentionally not recomputed after a `location.replace`).
74
+ let cachedHostOrigin;
75
+ /**
76
+ * Resolves the embedding host's origin from `document.referrer`, returning it
77
+ * only when it is whitelisted; otherwise `undefined` (non-whitelisted, blank,
78
+ * or unreadable referrer). The value is captured on the first call and
79
+ * memoized for the lifetime of the document.
80
+ */
81
+ const getTrustedHostOrigin = () => {
82
+ if (cachedHostOrigin) {
83
+ return cachedHostOrigin.value;
84
+ }
85
+ let value;
86
+ try {
87
+ const referrer = document.referrer;
88
+ const origin = referrer ? new URL(referrer).origin : undefined;
89
+ value = origin && isWhitelistedHostOrigin(origin) ? origin : undefined;
90
+ }
91
+ catch {
92
+ value = undefined;
93
+ }
94
+ cachedHostOrigin = { value };
95
+ return cachedHostOrigin.value;
96
+ };
97
+ /**
98
+ * The `allowedOrigins` to use for the child→host penpal handshake: the trusted
99
+ * whitelist when the embedding host origin resolves to a whitelisted domain,
100
+ * otherwise `[]` — fail closed. There is no `["*"]` fallback, so a parent that
101
+ * isn't a whitelisted Trackunit host can never complete the handshake.
102
+ */
103
+ const getHostConnectorAllowedOrigins = () => getTrustedHostOrigin() ? trustedHostOrigins : [];
104
+
4
105
  const doNothingByDefault = () => {
5
106
  // do nothing by default
6
107
  };
@@ -93,7 +194,10 @@ const setupHostConnector = (subscribedMethods, channel) => {
93
194
  ...subscribedMethods,
94
195
  };
95
196
  const connection = connect({
96
- messenger: new WindowMessenger({ remoteWindow: window.parent, allowedOrigins: ["*"] }),
197
+ // Pin the handshake to trusted Trackunit host origins. Degrades to ["*"]
198
+ // when the embedding host origin can't be resolved (older SDKs / unknown
199
+ // referrer) — see `trustedHostOrigin.ts`.
200
+ messenger: new WindowMessenger({ remoteWindow: window.parent, allowedOrigins: getHostConnectorAllowedOrigins() }),
97
201
  methods,
98
202
  channel,
99
203
  timeout: 10000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/iris-app-runtime-core",
3
- "version": "1.17.31-alpha-84ab66282fc.0",
3
+ "version": "1.17.31",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "penpal": "^7.0.6",
11
- "@trackunit/iris-app-runtime-core-api": "1.16.31-alpha-84ab66282fc.0"
11
+ "@trackunit/iris-app-runtime-core-api": "1.16.30"
12
12
  },
13
13
  "module": "./index.esm.js",
14
14
  "main": "./index.cjs.js",
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Trust anchor for the child (Iris app) side of the penpal bridge.
3
+ *
4
+ * An Iris app runs inside an iframe whose parent is the host (manager) page.
5
+ * Before this module the child connected with `allowedOrigins: ["*"]`, so any
6
+ * page could embed an app and drive it through the bridge. We pin the
7
+ * handshake to a *static whitelist* of Trackunit-controlled host domains — the
8
+ * whitelist, not the attacker-influenceable `document.referrer`, is the trust
9
+ * anchor.
10
+ *
11
+ * `document.referrer` is only used to *decide whether* we are confident enough
12
+ * to enforce the whitelist: the iframe sets `<meta name="referrer"
13
+ * content="strict-origin">`, so the referrer carries the embedding host's
14
+ * origin. When that origin resolves to a whitelisted Trackunit host we pin
15
+ * `allowedOrigins` to the whitelist; in every other case (non-whitelisted,
16
+ * blank, or unreadable referrer) we fail closed with `[]` so no parent can
17
+ * complete the handshake. There is deliberately no `["*"]` fallback — an
18
+ * embedder could blank or spoof its own referrer, so a wildcard fallback would
19
+ * re-open the exact unpinned path this module exists to close.
20
+ *
21
+ * The branded-domain list mirrors `BrandedUrls` in
22
+ * `libs/iris-app-sdk/iris-app-api/src/types/irisAppCspInput.ts`. It is
23
+ * duplicated here (as origin-matching RegExps rather than CSP `https://*.x`
24
+ * patterns) because `iris-app-runtime-core` does not depend on `iris-app-api`
25
+ * and adding that dependency for a static list isn't warranted. Keep the two
26
+ * lists in sync.
27
+ */
28
+ /**
29
+ * The static whitelist of trusted host origins, suitable for direct use as
30
+ * penpal `WindowMessenger.allowedOrigins` (which accepts `Array<string |
31
+ * RegExp>` and tests each incoming message origin against the list).
32
+ */
33
+ export declare const trustedHostOrigins: Array<string | RegExp>;
34
+ /**
35
+ * Whether the given window origin belongs to a trusted Trackunit host.
36
+ */
37
+ export declare const isWhitelistedHostOrigin: (origin: string) => boolean;
38
+ /**
39
+ * Resolves the embedding host's origin from `document.referrer`, returning it
40
+ * only when it is whitelisted; otherwise `undefined` (non-whitelisted, blank,
41
+ * or unreadable referrer). The value is captured on the first call and
42
+ * memoized for the lifetime of the document.
43
+ */
44
+ export declare const getTrustedHostOrigin: () => string | undefined;
45
+ /**
46
+ * The `allowedOrigins` to use for the child→host penpal handshake: the trusted
47
+ * whitelist when the embedding host origin resolves to a whitelisted domain,
48
+ * otherwise `[]` — fail closed. There is no `["*"]` fallback, so a parent that
49
+ * isn't a whitelisted Trackunit host can never complete the handshake.
50
+ */
51
+ export declare const getHostConnectorAllowedOrigins: () => Array<string | RegExp>;
52
+ /**
53
+ * Test-only: clears the capture-once cache so each test can exercise a
54
+ * different `document.referrer`. Not exported from the package index.
55
+ */
56
+ export declare const resetTrustedHostOriginCacheForTests: () => void;