@o3co/ts.hocon 1.9.0 → 1.11.0
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/README.ja.md +129 -1
- package/README.md +122 -2
- package/dist/adapters/env.cjs +1738 -0
- package/dist/adapters/env.d.cts +56 -0
- package/dist/adapters/env.d.ts +56 -0
- package/dist/adapters/env.js +1700 -0
- package/dist/adapters/jsonc.cjs +1716 -0
- package/dist/adapters/jsonc.d.cts +45 -0
- package/dist/adapters/jsonc.d.ts +45 -0
- package/dist/adapters/jsonc.js +1678 -0
- package/dist/adapters/properties.cjs +1744 -0
- package/dist/adapters/properties.d.cts +32 -0
- package/dist/adapters/properties.d.ts +32 -0
- package/dist/adapters/properties.js +1707 -0
- package/dist/adapters/toml.cjs +1629 -0
- package/dist/adapters/toml.d.cts +18 -0
- package/dist/adapters/toml.d.ts +18 -0
- package/dist/adapters/toml.js +1594 -0
- package/dist/adapters/yaml.cjs +1686 -0
- package/dist/adapters/yaml.d.cts +51 -0
- package/dist/adapters/yaml.d.ts +51 -0
- package/dist/adapters/yaml.js +1650 -0
- package/dist/{config-D27s3_aG.d.cts → config-Dlq6rF3w.d.cts} +50 -4
- package/dist/{config-D27s3_aG.d.ts → config-Dlq6rF3w.d.ts} +50 -4
- package/dist/index.cjs +207 -35
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +214 -35
- package/dist/zod.cjs +203 -33
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/dist/zod.js +210 -33
- package/package.json +89 -19
package/README.ja.md
CHANGED
|
@@ -24,6 +24,20 @@ npm install @o3co/ts.hocon
|
|
|
24
24
|
|
|
25
25
|
Node.js 22 以上が必要です。
|
|
26
26
|
|
|
27
|
+
ESM / CJS の両方に対応しています。`import` でも `require` でも同じエントリポイント
|
|
28
|
+
(`adapters/*` のサブパスを含む)に到達します:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { parse } from '@o3co/ts.hocon' // ESM
|
|
32
|
+
const { parse } = require('@o3co/ts.hocon') // CJS
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
CI では PR ごと・リリースごとに、ビルド成果物に対して 7 つの export すべてを両形式で
|
|
36
|
+
ロードし、実際に呼び出し(バンドル形式で挙動が変わる package include を含む)、各条件が
|
|
37
|
+
自前の型定義を宣言しているかまで検証します。1.10.0 で CJS の 7 エントリポイントすべてが
|
|
38
|
+
ロード時に例外を投げた実績があるためのゲートであり、この種の欠陥を検出するものです
|
|
39
|
+
(あらゆる欠陥が起こり得ないという保証ではありません)。
|
|
40
|
+
|
|
27
41
|
### 2. 使い方
|
|
28
42
|
|
|
29
43
|
```ts
|
|
@@ -67,7 +81,7 @@ HOCON は単なるシリアライズ形式ではなく、**プログラムに注
|
|
|
67
81
|
- `include "file.conf"` および `include file("file.conf")` ディレクティブ
|
|
68
82
|
- トリプルクォート文字列(`"""..."""`)
|
|
69
83
|
- 同期・非同期 API(`parse` / `parseAsync` / `parseFile` / `parseFileAsync`)
|
|
70
|
-
- ESM + CJS
|
|
84
|
+
- ESM + CJS デュアルパッケージ(全エントリポイントを CI で両形式ロード検証)
|
|
71
85
|
- [Zod](https://zod.dev/) スキーマバリデーション統合(オプション)
|
|
72
86
|
- ブラウザ対応(`parse`/`parseAsync` — Node.js 不要)
|
|
73
87
|
|
|
@@ -268,6 +282,16 @@ const cfg = await parseAsync(hoconString, {
|
|
|
268
282
|
- **`${ENV}` の使用を最小限に**: 設定ファイル自体にデフォルト値を定義し、`${?ENV}`(オプショナル)を使いましょう
|
|
269
283
|
- **ローカル開発で環境変数を必須にしない**: デフォルトだけで動くようにしましょう
|
|
270
284
|
- **必須の環境変数を文書化**: プロジェクトの README や `.env.example` にリストしましょう
|
|
285
|
+
- **変数名の階層は `.` ではなく `__` で**: `loadEnv`(および `parseDotEnv`)で一括マウント
|
|
286
|
+
するとき、階層を作るのは `__` **だけ** です。`.` は通常のキー文字なので、次の 2 つは
|
|
287
|
+
別々のキーであり、どちらも失われません:
|
|
288
|
+
|
|
289
|
+
```text
|
|
290
|
+
APP_FOO__BAR=nested # -> foo.bar : cfg.getString('foo.bar')
|
|
291
|
+
APP_FOO.BAR=dotted # -> "foo.bar" : cfg.getString('"foo.bar"')
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
単独の `_` もセグメントの一部のままです(`APP_DB__MAX_CONN` → `db.max_conn`)。
|
|
271
295
|
|
|
272
296
|
### 開発 / 本番の分離
|
|
273
297
|
|
|
@@ -304,6 +328,110 @@ const config = parseWithSchema(hoconInput, schema) // 起動時に即座に失
|
|
|
304
328
|
|
|
305
329
|
3 つのパーサー実装([ts.hocon](https://github.com/o3co/ts.hocon)、[rs.hocon](https://github.com/o3co/rs.hocon)、[go.hocon](https://github.com/o3co/go.hocon))はすべて同じ Lightbend HOCON 仕様で追跡されています — 実装ごとの準拠率は [横断ロールアップ](https://github.com/o3co/xx.hocon/blob/main/docs/compliance-matrix.md) を参照してください。
|
|
306
330
|
|
|
331
|
+
## フォーマットアダプタ
|
|
332
|
+
|
|
333
|
+
*他のプログラム* が所有する設定ファイルを HOCON としてマウントできます。自分の
|
|
334
|
+
ドキュメント内の `${...}` からその内容を参照できるようになります:
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
import { parseStringWithOptions } from '@o3co/ts.hocon'
|
|
338
|
+
import { loadEnv } from '@o3co/ts.hocon/adapters/env'
|
|
339
|
+
|
|
340
|
+
const base = loadEnv({ prefix: 'APP_' }) // APP_DB__HOST -> db.host
|
|
341
|
+
const cfg = parseStringWithOptions(src, { resolveSubstitutions: false })
|
|
342
|
+
const merged = cfg.withFallback(base).resolve()
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
解決を遅延させることが重要です。素の `parse` はパースしながら解決するため、
|
|
346
|
+
フォールバックを付ける前に `${...}` の解決が失敗してしまいます。
|
|
347
|
+
|
|
348
|
+
| サブパス | 必要な依存 | 備考 |
|
|
349
|
+
| --- | --- | --- |
|
|
350
|
+
| `@o3co/ts.hocon/adapters/properties` | — | `java.util.Properties`。`include` と構文レイヤーを共有 |
|
|
351
|
+
| `@o3co/ts.hocon/adapters/env` | — | プレフィックス付き名前空間の一括マウント。`.env` の読み込みも可能 |
|
|
352
|
+
| `@o3co/ts.hocon/adapters/jsonc` | — | コメントと末尾カンマを許す JSON |
|
|
353
|
+
| `@o3co/ts.hocon/adapters/toml` | `smol-toml` | オプショナルなピア依存 |
|
|
354
|
+
| `@o3co/ts.hocon/adapters/yaml` | `yaml` 2.9.x | オプショナルなピア依存。スカラー解決はそのライブラリの答えで、`version: '1.2'` を明示してドリフトを防止 |
|
|
355
|
+
|
|
356
|
+
TOML と YAML のライブラリは **オプショナルなピア依存** なので、本パッケージを入れても
|
|
357
|
+
何も追加でインストールされません — 実際に使うものだけを足してください。プレーンな JSON に
|
|
358
|
+
アダプタは不要です(HOCON は JSON のスーパーセットです)。
|
|
359
|
+
|
|
360
|
+
外部のデータはあくまでデータです。マウントされた値の中の `${a.b}` は参照ではなく
|
|
361
|
+
リテラルなテキストとして扱われます。そのファイルは HOCON の構文に同意していない
|
|
362
|
+
プログラムのものだからです。
|
|
363
|
+
|
|
364
|
+
### 環境変数名の付け方
|
|
365
|
+
|
|
366
|
+
パス区切りは `__` だけです。変数名の中の `.` は境界ではなくキー文字なので、
|
|
367
|
+
`APP_FOO.BAR` と `APP_FOO__BAR` は別のキーであり、同時に設定できます:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
const cfg = loadEnv({ prefix: 'APP_', env: { 'APP_FOO.BAR': 'dotted', APP_FOO__BAR: 'nested' } })
|
|
371
|
+
cfg.getString('"foo.bar"') // "dotted" — 名前にドットを含む 1 つのキー
|
|
372
|
+
cfg.getString('foo.bar') // "nested" — foo -> bar
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### JSONC / YAML の数値
|
|
376
|
+
|
|
377
|
+
整数は精度を落とさずに取り込まれます。JS の `number` で表せない幅の整数リテラルも
|
|
378
|
+
桁がそのまま保持され、int64 の範囲を超える値は黙って壊れる代わりにエラーになります。
|
|
379
|
+
取得結果はゲッターによって変わります:
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
const cfg = parseJsonc('{"id": 9007199254740993}')
|
|
383
|
+
cfg.getString('id') // "9007199254740993" — ソースそのままの正確な値
|
|
384
|
+
cfg.getNumber('id') // 9007199254740992 — JS の数値モデルで丸められる
|
|
385
|
+
cfg.toObject() // { id: 9007199254740992 }
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
したがって大きな識別子(snowflake ID、台帳の連番など)は `getString` で読んでください。
|
|
389
|
+
`getNumber` と `toObject` は JavaScript の数値セマンティクスをそのまま適用します
|
|
390
|
+
(HOCON テキストに同じリテラルを書いた場合と同じ挙動です)。保証されるのは
|
|
391
|
+
**取り込み時に情報が失われない** ことです。
|
|
392
|
+
|
|
393
|
+
YAML のスカラー解決はそれ以外の点では `yaml` ライブラリのものです。アダプタは
|
|
394
|
+
デフォルト任せにせず `version: '1.2'` を明示し、`fromYamlValue` はデコード済みの
|
|
395
|
+
ツリーを受け取るため、別のライブラリやスキーマを使うこともできます。
|
|
396
|
+
|
|
397
|
+
## 既知の制限
|
|
398
|
+
|
|
399
|
+
- **`include url(...)`** は未対応です。リモート設定の取得はこのパーサーの範囲外です。アプリの HTTP クライアントで取得してから `parse()` に渡してください。
|
|
400
|
+
- **`include classpath(...)`** は未対応です。JVM 固有の include 形式で、Java ランタイム以外に対応物がありません。
|
|
401
|
+
- **watch / reload なし** — ライブラリはロード時にパースするだけです。ライブリロードが必要なら変更時に `parse()` / `parseFile()` を呼び直してください。
|
|
402
|
+
- **ストリーミングパーサーなし** — 入力全体をメモリに載せます。非常に大きな設定はパース前にサイズを検証してください(セキュリティ上の考慮事項を参照)。
|
|
403
|
+
|
|
404
|
+
## セキュリティ上の考慮事項
|
|
405
|
+
|
|
406
|
+
信頼できない HOCON 入力をパースする際は次に注意してください:
|
|
407
|
+
|
|
408
|
+
- **include のパストラバーサル:** 相対 `include` パスは `baseDir` から解決され、`..` セグメントで外に出て `/etc/passwd` のような機密ファイルに到達し得ます。信頼できない入力をパースするなら、パスを検証するカスタム `readFileSync`/`readFile` を渡してください。
|
|
409
|
+
- **入力サイズ:** パーサーに入力サイズ上限はありません。信頼できない入力は `parse()` の前にサイズを検証してください。
|
|
410
|
+
- **include の深さ:** 深い include 連鎖によるスタックオーバーフローを防ぐため 50 段に制限されています。
|
|
411
|
+
- **プロトタイプ汚染:** `__proto__` / `constructor` / `prototype` という名前のキーは
|
|
412
|
+
**通常のキーとして保持されます**。`.properties` ファイルや環境変数がそれらを正当に
|
|
413
|
+
使っていることがあり、落とせば黙ったデータ欠落になるからです。ライブラリ内部の安全性は
|
|
414
|
+
拒否リストではなく構造で担保しています。ネスト構築には `__proto__` セッターを継承しない
|
|
415
|
+
null プロトタイプのオブジェクトを使い、各アダプタと `toObject()` はキーを own データ
|
|
416
|
+
プロパティとして定義します。パース自体は何も汚染せず、返るオブジェクトのプロトタイプは
|
|
417
|
+
常に `Object.prototype` です。
|
|
418
|
+
|
|
419
|
+
**重要なのは結果をどう扱うか** です。設定データが own な `__proto__` キーを持ち得ます:
|
|
420
|
+
|
|
421
|
+
```ts
|
|
422
|
+
const data = cfg.toObject()
|
|
423
|
+
{ ...data } // 安全
|
|
424
|
+
structuredClone(data) // 安全
|
|
425
|
+
Object.assign({}, data) // 危険 — キーが消え、設定データがコピー先のプロトタイプになる
|
|
426
|
+
deepMerge({}, data) // 危険 — 素朴な再帰マージはグローバルな Object.prototype に書き込み得る
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
いずれも **コピー先** オブジェクトのプロトタイプに起因するため、避けるのは利用側の責務です。
|
|
430
|
+
コピーはスプレッドか `structuredClone`、走査は `Object.entries()` / `Object.keys()` を使い、
|
|
431
|
+
`__proto__` を通常のキー名として扱ってください。制御下にない場所へ渡す場合は
|
|
432
|
+
`cfg.toObject({ nullPrototype: true })` が何も継承しないツリーを返します(ただし利用側が
|
|
433
|
+
素の `{}` にコピーする場合の問題までは解消できません)。
|
|
434
|
+
|
|
307
435
|
## ライセンス
|
|
308
436
|
|
|
309
437
|
Apache License 2.0 — [LICENSE](LICENSE) を参照。
|
package/README.md
CHANGED
|
@@ -28,6 +28,21 @@ npm install @o3co/ts.hocon
|
|
|
28
28
|
|
|
29
29
|
Requires Node.js 22+.
|
|
30
30
|
|
|
31
|
+
Both module systems are supported — `import` and `require` reach the same
|
|
32
|
+
entrypoints, including every `adapters/*` subpath:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { parse } from '@o3co/ts.hocon' // ESM
|
|
36
|
+
const { parse } = require('@o3co/ts.hocon') // CJS
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
On every PR and every release, CI loads all seven exports both ways against the
|
|
40
|
+
built artifact, calls each one (including a package-scoped `include`, which
|
|
41
|
+
behaves differently per bundle format), and checks that each condition declares
|
|
42
|
+
its own type declarations. That gate exists because 1.10.0 shipped with all
|
|
43
|
+
seven CJS entrypoints throwing at load; it catches that class of defect rather
|
|
44
|
+
than promising none can exist.
|
|
45
|
+
|
|
31
46
|
### 2. Use
|
|
32
47
|
|
|
33
48
|
```ts
|
|
@@ -72,7 +87,7 @@ On top of that, HOCON combines the readability of YAML with the structure of JSO
|
|
|
72
87
|
- Triple-quoted strings (`"""..."""`)
|
|
73
88
|
- Duration and byte size parsing (`getDuration()`, `getBytes()`)
|
|
74
89
|
- Sync and async API (`parse` / `parseAsync` / `parseFile` / `parseFileAsync`)
|
|
75
|
-
- ESM + CJS dual package
|
|
90
|
+
- ESM + CJS dual package (every entrypoint smoke-tested in both formats on each CI run)
|
|
76
91
|
- Optional [Zod](https://zod.dev/) integration for schema validation
|
|
77
92
|
- Browser compatible (`parse`/`parseAsync` — no Node.js required)
|
|
78
93
|
|
|
@@ -354,6 +369,17 @@ const cfg = await parseAsync(hoconString, {
|
|
|
354
369
|
- **Minimize `${ENV}` usage**: Prefer `${?ENV}` (optional) with sensible defaults defined in the config itself
|
|
355
370
|
- **Never require env vars for local development**: Defaults should work out of the box
|
|
356
371
|
- **Document required env vars**: List them in your project's README or a `.env.example`
|
|
372
|
+
- **Name variables with `__`, never `.`**: when bulk-mounting with `loadEnv` (or
|
|
373
|
+
`parseDotEnv`), `__` is the *only* thing that creates hierarchy. A dot is
|
|
374
|
+
ordinary key text, so the two spellings below are different keys and both
|
|
375
|
+
survive:
|
|
376
|
+
|
|
377
|
+
```text
|
|
378
|
+
APP_FOO__BAR=nested # -> foo.bar : cfg.getString('foo.bar')
|
|
379
|
+
APP_FOO.BAR=dotted # -> "foo.bar" : cfg.getString('"foo.bar"')
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
A single `_` also stays part of its segment (`APP_DB__MAX_CONN` → `db.max_conn`).
|
|
357
383
|
|
|
358
384
|
### Dev / Prod Separation
|
|
359
385
|
|
|
@@ -391,13 +417,79 @@ const config = parseWithSchema(hoconInput, schema) // fails fast on startup
|
|
|
391
417
|
|
|
392
418
|
The four parser implementations ([ts.hocon](https://github.com/o3co/ts.hocon), [rs.hocon](https://github.com/o3co/rs.hocon), [go.hocon](https://github.com/o3co/go.hocon), [py.hocon](https://github.com/o3co/py.hocon)) are all tracked against the same Lightbend HOCON spec — see the [cross-impl roll-up](https://github.com/o3co/xx.hocon/blob/main/docs/compliance-matrix.md) for per-impl conformance rates.
|
|
393
419
|
|
|
420
|
+
## Format adapters
|
|
421
|
+
|
|
422
|
+
Config files that belong to *other* programs can be mounted as HOCON, so a
|
|
423
|
+
`${...}` in your document can reach into them:
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
import { parseStringWithOptions } from '@o3co/ts.hocon'
|
|
427
|
+
import { loadEnv } from '@o3co/ts.hocon/adapters/env'
|
|
428
|
+
|
|
429
|
+
const base = loadEnv({ prefix: 'APP_' }) // APP_DB__HOST -> db.host
|
|
430
|
+
const cfg = parseStringWithOptions(src, { resolveSubstitutions: false })
|
|
431
|
+
const merged = cfg.withFallback(base).resolve()
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Deferring resolution matters: the plain `parse` resolves as it goes, so a
|
|
435
|
+
`${...}` aimed at the fallback would fail before the fallback is attached.
|
|
436
|
+
|
|
437
|
+
| Subpath | Needs | Notes |
|
|
438
|
+
| --- | --- | --- |
|
|
439
|
+
| `@o3co/ts.hocon/adapters/properties` | — | `java.util.Properties`, sharing the `include` syntax layer |
|
|
440
|
+
| `@o3co/ts.hocon/adapters/env` | — | Bulk-mounts a prefixed namespace; also reads `.env` |
|
|
441
|
+
| `@o3co/ts.hocon/adapters/jsonc` | — | JSON with comments and trailing commas |
|
|
442
|
+
| `@o3co/ts.hocon/adapters/toml` | `smol-toml` | Optional peer dependency |
|
|
443
|
+
| `@o3co/ts.hocon/adapters/yaml` | `yaml` 2.9.x | Optional peer dependency; scalar resolution is that library's answer, with `version: '1.2'` declared so it cannot drift |
|
|
444
|
+
|
|
445
|
+
The TOML and YAML libraries are **optional peer dependencies**, so installing
|
|
446
|
+
this package still pulls in nothing — you add the one you actually use. Plain
|
|
447
|
+
JSON needs no adapter at all, HOCON being a JSON superset.
|
|
448
|
+
|
|
449
|
+
Foreign data stays data: a `${a.b}` in a mounted value is literal text, never a
|
|
450
|
+
reference, because the file belongs to a program that never agreed to HOCON's
|
|
451
|
+
syntax.
|
|
452
|
+
|
|
453
|
+
### Environment variable names
|
|
454
|
+
|
|
455
|
+
`__` is the only path separator, so a literal `.` in a variable name is key
|
|
456
|
+
text rather than a boundary — `APP_FOO.BAR` and `APP_FOO__BAR` are two
|
|
457
|
+
different keys and can be set at the same time:
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
const cfg = loadEnv({ prefix: 'APP_', env: { 'APP_FOO.BAR': 'dotted', APP_FOO__BAR: 'nested' } })
|
|
461
|
+
cfg.getString('"foo.bar"') // "dotted" — one key whose name contains a dot
|
|
462
|
+
cfg.getString('foo.bar') // "nested" — foo -> bar
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Numbers from JSONC and YAML
|
|
466
|
+
|
|
467
|
+
Integers are ingested losslessly: an integer literal too wide for a JS `number`
|
|
468
|
+
keeps its digits instead of being rounded, and one outside the int64 range is
|
|
469
|
+
refused rather than silently mangled. What you get back depends on the getter:
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
const cfg = parseJsonc('{"id": 9007199254740993}')
|
|
473
|
+
cfg.getString('id') // "9007199254740993" — exact, the source's own text
|
|
474
|
+
cfg.getNumber('id') // 9007199254740992 — the JS number model rounds
|
|
475
|
+
cfg.toObject() // { id: 9007199254740992 }
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
So read large identifiers (snowflake IDs, ledger sequence numbers) with
|
|
479
|
+
`getString`. `getNumber` and `toObject` apply JavaScript's own number
|
|
480
|
+
semantics, exactly as they do for the same literal written in HOCON text — the
|
|
481
|
+
guarantee is that nothing is lost *on the way in*.
|
|
482
|
+
|
|
483
|
+
YAML scalar resolution otherwise belongs to the `yaml` library; the adapter
|
|
484
|
+
declares `version: '1.2'` rather than trusting a default, and `fromYamlValue`
|
|
485
|
+
takes an already-decoded tree so you can use a different library or schema.
|
|
486
|
+
|
|
394
487
|
## Known Limitations
|
|
395
488
|
|
|
396
489
|
- **`include url(...)`** is not supported. Fetching remote configuration is outside the scope of this parser. Use your application's HTTP client to fetch the content, then pass it to `parse()`.
|
|
397
490
|
- **`include classpath(...)`** is not supported. This is a JVM-specific include form with no equivalent outside Java runtimes.
|
|
398
491
|
- **No watch/reload** — the library parses config at load time. For live-reloading, re-call `parse()` or `parseFile()` on change.
|
|
399
492
|
- **No streaming parser** — the entire input is loaded into memory. For very large configs, validate input size before parsing (see Security Considerations).
|
|
400
|
-
- **`.properties` include** — supports basic `key=value` / `key:value` syntax. Does not support multiline values (backslash continuation), Unicode escapes, or key escaping from the full Java .properties specification.
|
|
401
493
|
|
|
402
494
|
## Security Considerations
|
|
403
495
|
|
|
@@ -406,6 +498,34 @@ When parsing untrusted HOCON input, be aware of:
|
|
|
406
498
|
- **Path traversal in includes:** a relative `include` path resolves against `baseDir` and can climb out of it with `..` segments to reach sensitive files such as `/etc/passwd`. Use a custom `readFileSync`/`readFile` that validates paths if parsing untrusted input.
|
|
407
499
|
- **Input size:** The parser has no built-in input size limit. For untrusted input, validate size before calling `parse()`.
|
|
408
500
|
- **Include depth:** Limited to 50 levels to prevent stack overflow from deep include chains.
|
|
501
|
+
- **Prototype pollution:** keys named `__proto__`, `constructor` or `prototype`
|
|
502
|
+
are **kept as ordinary keys** — a `.properties` file or environment variable
|
|
503
|
+
may legitimately use them, and dropping them would be silent data loss.
|
|
504
|
+
Safety inside this library is structural rather than a denylist: the nesting
|
|
505
|
+
step builds null-prototype objects, which inherit no `__proto__` setter, every
|
|
506
|
+
adapter materializes objects by defining own data properties, and `toObject()`
|
|
507
|
+
does the same. Parsing pollutes nothing, and the returned object's prototype is
|
|
508
|
+
always `Object.prototype`.
|
|
509
|
+
|
|
510
|
+
**What you do with the result matters**, because config data can now hand you
|
|
511
|
+
an own `__proto__` key:
|
|
512
|
+
|
|
513
|
+
```ts
|
|
514
|
+
const data = cfg.toObject()
|
|
515
|
+
{ ...data } // safe
|
|
516
|
+
structuredClone(data) // safe
|
|
517
|
+
Object.assign({}, data) // NOT safe — the key vanishes and config data becomes
|
|
518
|
+
// the copy's prototype
|
|
519
|
+
deepMerge({}, data) // NOT safe — a naive recursive merge can write onto
|
|
520
|
+
// the global Object.prototype
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
Both hazards come from the *destination* object's prototype, so they are the
|
|
524
|
+
consumer's to avoid: copy with spread or `structuredClone`, iterate with
|
|
525
|
+
`Object.entries()` / `Object.keys()`, and treat `__proto__` as an ordinary
|
|
526
|
+
key name. `cfg.toObject({ nullPrototype: true })` returns a tree that inherits
|
|
527
|
+
nothing — useful when the data is going somewhere you do not control, though it
|
|
528
|
+
cannot fix a consumer that copies into a plain `{}`.
|
|
409
529
|
|
|
410
530
|
## License
|
|
411
531
|
|