locizify 9.0.8 → 9.0.10

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
@@ -1,3 +1,15 @@
1
+ ### 9.0.10
2
+
3
+ Security release — includes upstream fixes from `i18next-locize-backend`, `i18nextify`, and `locize`.
4
+
5
+ - security: emit a one-time `console.warn` when `?apikey=` or `?projectid=` is read from the URL query string on a non-local host (anything other than `localhost`, `127.0.0.1`, `::1`, `*.localhost`, `*.local`). The feature itself is preserved — an attacker-crafted link on a production host could otherwise silently redirect translations (and `saveMissing` writes) to an attacker-chosen locize project (CWE-522); the warning is there so maintainers notice when it happens and can decide to disable the URL-credential path in their deployment. Prefer configuring credentials via the `<script id="locizify" apikey="…" projectid="…">` attributes, which are not attacker-controllable.
6
+ - chore: bump pinned deps (security releases): `i18next-locize-backend` 9.0.1 → **9.0.2** ([GHSA-mgcp-mfp8-3q45](https://github.com/locize/i18next-locize-backend/security/advisories/GHSA-mgcp-mfp8-3q45)), `i18nextify` 4.0.7 → **4.0.8** ([GHSA-6457-mxpq-4fqq](https://github.com/i18next/i18nextify/security/advisories/GHSA-6457-mxpq-4fqq)), `locize` 4.0.16 → **4.0.21** ([GHSA-w937-fg2h-xhq2](https://github.com/locize/locize/security/advisories/GHSA-w937-fg2h-xhq2)).
7
+ - chore: ignore `.env*` and `*.pem`/`*.key` files in `.gitignore`.
8
+
9
+ ### 9.0.9
10
+
11
+ - update i18nextify
12
+
1
13
  ### 9.0.8
2
14
 
3
15
  - update i18nextify
@@ -35,6 +35,30 @@ function getQsParameterByName(name, url) {
35
35
  if (!results[2]) return '';
36
36
  return decodeURIComponent(results[2].replace(/\+/g, ' '));
37
37
  }
38
+
39
+ // Reading credentials from the URL query string is convenient for local
40
+ // development (run `?apikey=...&projectId=...` against a demo page) but is
41
+ // dangerous on deployed sites: an attacker-crafted link would cause the
42
+ // victim's page to send translation data (saveMissing) to the attacker's
43
+ // locize project, or to run against an attacker-chosen backend.
44
+ // The feature is preserved, but a warning is emitted when the URL
45
+ // overrides credential values on hosts that don't look like a local dev
46
+ // environment, so maintainers can notice and decide whether to disable it.
47
+ function isLocalDevHost() {
48
+ if (typeof window === 'undefined' || !window.location) return false;
49
+ var h = window.location.hostname;
50
+ if (!h) return false;
51
+ return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '0.0.0.0' || h.endsWith('.localhost') || h.endsWith('.local');
52
+ }
53
+ var credentialWarningShown = false;
54
+ function warnIfCredentialFromUrlOnProdHost(attr) {
55
+ if (isLocalDevHost()) return;
56
+ if (credentialWarningShown) return; // only once per page
57
+ credentialWarningShown = true;
58
+ if (typeof console !== 'undefined' && typeof console.warn === 'function') {
59
+ console.warn('locizify: reading credential "' + attr + '" from URL query string on a non-local host. ' + 'An attacker-crafted link can replace your locize credentials, redirecting saveMissing writes ' + 'to an attacker-chosen project. Prefer configuring credentials via the ' + '<script id="locizify" apikey="..." projectid="..."> attributes instead.');
60
+ }
61
+ }
38
62
  var originalInit = i18next.init;
39
63
  i18next.init = function () {
40
64
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -70,11 +94,20 @@ i18next.init = function () {
70
94
  if (attr.toLowerCase() === 'autopilot' && value === '') value = true;
71
95
  if (value !== undefined && value !== null) backend[attr] = value;
72
96
  if (!value) {
73
- value = getQsParameterByName(attr.toLowerCase());
97
+ var lc = attr.toLowerCase();
98
+ value = getQsParameterByName(lc);
74
99
  if (value === 'true') value = true;
75
100
  if (value === 'false') value = false;
76
- if (attr.toLowerCase() === 'autopilot' && value === '') value = true;
77
- if (value !== undefined && value !== null) backend[attr] = value;
101
+ if (lc === 'autopilot' && value === '') value = true;
102
+ if (value !== undefined && value !== null) {
103
+ backend[attr] = value;
104
+ // Credential-bearing attributes via URL on a non-local host is
105
+ // risky — attacker-crafted links can replace your credentials.
106
+ // Warn once per page load so maintainers notice.
107
+ if (lc === 'apikey' || lc === 'projectid') {
108
+ warnIfCredentialFromUrlOnProdHost(lc);
109
+ }
110
+ }
78
111
  }
79
112
  });
80
113
  if (backend.allowedAddOrUpdateHost) {
@@ -90,8 +123,14 @@ i18next.init = function () {
90
123
  // call orginal callback
91
124
  callback(err, t);
92
125
  }
126
+
127
+ // Accept `?apikey=` from the URL query string as a fallback. On non-local
128
+ // hosts this is risky (attacker-crafted links can replace your credentials
129
+ // and redirect saveMissing writes to an attacker-chosen project), so warn
130
+ // once when it happens.
93
131
  if (!options.backend.apiKey && getQsParameterByName('apikey')) {
94
132
  options.backend.apiKey = getQsParameterByName('apikey');
133
+ warnIfCredentialFromUrlOnProdHost('apikey');
95
134
  }
96
135
  if (!options.backend.autoPilot || options.backend.autoPilot === 'false') {
97
136
  return originalInit.call(i18next, _objectSpread(_objectSpread({}, options), enforce), handleI18nextInitialized);
package/dist/es/index.js CHANGED
@@ -28,6 +28,30 @@ function getQsParameterByName(name, url) {
28
28
  if (!results[2]) return '';
29
29
  return decodeURIComponent(results[2].replace(/\+/g, ' '));
30
30
  }
31
+
32
+ // Reading credentials from the URL query string is convenient for local
33
+ // development (run `?apikey=...&projectId=...` against a demo page) but is
34
+ // dangerous on deployed sites: an attacker-crafted link would cause the
35
+ // victim's page to send translation data (saveMissing) to the attacker's
36
+ // locize project, or to run against an attacker-chosen backend.
37
+ // The feature is preserved, but a warning is emitted when the URL
38
+ // overrides credential values on hosts that don't look like a local dev
39
+ // environment, so maintainers can notice and decide whether to disable it.
40
+ function isLocalDevHost() {
41
+ if (typeof window === 'undefined' || !window.location) return false;
42
+ var h = window.location.hostname;
43
+ if (!h) return false;
44
+ return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '0.0.0.0' || h.endsWith('.localhost') || h.endsWith('.local');
45
+ }
46
+ var credentialWarningShown = false;
47
+ function warnIfCredentialFromUrlOnProdHost(attr) {
48
+ if (isLocalDevHost()) return;
49
+ if (credentialWarningShown) return; // only once per page
50
+ credentialWarningShown = true;
51
+ if (typeof console !== 'undefined' && typeof console.warn === 'function') {
52
+ console.warn('locizify: reading credential "' + attr + '" from URL query string on a non-local host. ' + 'An attacker-crafted link can replace your locize credentials, redirecting saveMissing writes ' + 'to an attacker-chosen project. Prefer configuring credentials via the ' + '<script id="locizify" apikey="..." projectid="..."> attributes instead.');
53
+ }
54
+ }
31
55
  var originalInit = i18next.init;
32
56
  i18next.init = function () {
33
57
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -63,11 +87,20 @@ i18next.init = function () {
63
87
  if (attr.toLowerCase() === 'autopilot' && value === '') value = true;
64
88
  if (value !== undefined && value !== null) backend[attr] = value;
65
89
  if (!value) {
66
- value = getQsParameterByName(attr.toLowerCase());
90
+ var lc = attr.toLowerCase();
91
+ value = getQsParameterByName(lc);
67
92
  if (value === 'true') value = true;
68
93
  if (value === 'false') value = false;
69
- if (attr.toLowerCase() === 'autopilot' && value === '') value = true;
70
- if (value !== undefined && value !== null) backend[attr] = value;
94
+ if (lc === 'autopilot' && value === '') value = true;
95
+ if (value !== undefined && value !== null) {
96
+ backend[attr] = value;
97
+ // Credential-bearing attributes via URL on a non-local host is
98
+ // risky — attacker-crafted links can replace your credentials.
99
+ // Warn once per page load so maintainers notice.
100
+ if (lc === 'apikey' || lc === 'projectid') {
101
+ warnIfCredentialFromUrlOnProdHost(lc);
102
+ }
103
+ }
71
104
  }
72
105
  });
73
106
  if (backend.allowedAddOrUpdateHost) {
@@ -83,8 +116,14 @@ i18next.init = function () {
83
116
  // call orginal callback
84
117
  callback(err, t);
85
118
  }
119
+
120
+ // Accept `?apikey=` from the URL query string as a fallback. On non-local
121
+ // hosts this is risky (attacker-crafted links can replace your credentials
122
+ // and redirect saveMissing writes to an attacker-chosen project), so warn
123
+ // once when it happens.
86
124
  if (!options.backend.apiKey && getQsParameterByName('apikey')) {
87
125
  options.backend.apiKey = getQsParameterByName('apikey');
126
+ warnIfCredentialFromUrlOnProdHost('apikey');
88
127
  }
89
128
  if (!options.backend.autoPilot || options.backend.autoPilot === 'false') {
90
129
  return originalInit.call(i18next, _objectSpread(_objectSpread({}, options), enforce), handleI18nextInitialized);