yamlock 0.3.0 → 1.1.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/CHANGELOG.md +205 -0
- package/README.md +274 -41
- package/bin/yamlock +8 -0
- package/dist/cli/cli.js +489 -79
- package/dist/crypto/decrypt.js +112 -13
- package/dist/crypto/encrypt.js +107 -18
- package/dist/crypto/payload-v2.js +371 -0
- package/dist/crypto/utils.js +65 -14
- package/dist/errors.js +60 -0
- package/dist/index.d.ts +125 -0
- package/dist/index.js +10 -0
- package/dist/utils/config.js +331 -54
- package/dist/utils/file.js +58 -0
- package/dist/utils/migrate.js +176 -0
- package/dist/utils/path-pattern.js +260 -0
- package/dist/utils/path.js +58 -9
- package/docs/api.md +63 -0
- package/docs/design/path-patterns.md +213 -0
- package/docs/design/payload-v2.md +344 -0
- package/docs/errors.md +72 -0
- package/docs/yaml-behavior.md +52 -0
- package/examples/basic.js +31 -0
- package/examples/docs/ci-cd.md +58 -0
- package/examples/docs/key-rotation.md +65 -0
- package/package.json +19 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. Release versions are finalized in dedicated commits and use matching numeric Git tags.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [1.1.0] - 2026-08-14
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Tag-gated GitHub Release automation that reuses the full CI matrix, validates the tag against `main`, derives notes from the matching changelog section, verifies a draft, and publishes the release as latest.
|
|
11
|
+
- Structural `pathPatterns` / `--path-patterns` selectors for `processConfig` and CLI encrypt/decrypt/migrate workflows, with whole-segment `*`, `[*]`, and `**` matching, exact-path authentication, and fail-closed validation without reinterpreting existing `paths`.
|
|
12
|
+
|
|
13
|
+
## [1.0.0] - 2026-08-11
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- Authenticated payload v2 for the Node.js API, using AES-256-GCM, scrypt, strict parsing, and authenticated field-path metadata.
|
|
17
|
+
- Frozen legacy payload fixtures and v2 regression tests for deterministic vectors, tampering, malformed input, mixed-format configs, and resource limits.
|
|
18
|
+
- Payload v2 design documentation covering the threat model, serialized format, compatibility, and staged migration.
|
|
19
|
+
- Safe `yamlock migrate` workflow with dry-run summaries, selective paths, mixed-format validation, explicit backups, separate outputs, and atomic permission-preserving writes.
|
|
20
|
+
- Explicit CLI legacy-write compatibility through `yamlock encrypt --legacy`; custom encryption algorithms require this mode.
|
|
21
|
+
- Strict repeated-encryption checks through CLI `--error-on-encrypted` and Node.js `existingPayloadPolicy: 'error'`.
|
|
22
|
+
- Explicit `--force-encrypt` / `existingPayloadPolicy: 'encrypt'` handling for intentional `yl|...` plaintext or nested encryption.
|
|
23
|
+
- YAML rewrite documentation and regression coverage for comments, formatting, anchors, aliases, merge keys, and explicit/custom tags.
|
|
24
|
+
- Public `serializePath(segments)` helper for constructing canonical field paths without string ambiguity.
|
|
25
|
+
- Edge-case regression coverage for empty containers, sparse arrays, special and Unicode keys, empty strings, and large values.
|
|
26
|
+
- Automated npm tarball smoke coverage for package contents, public API imports, and the installed distribution-first CLI.
|
|
27
|
+
- Stable public Node.js error classes and `ERR_*` codes for validation, payload, authentication, legacy decryption, and config-processing failures.
|
|
28
|
+
- Bundled TypeScript declarations and a documented `1.x` stability contract for the public Node.js API.
|
|
29
|
+
- Built-in coverage thresholds plus automated Markdown-link and release-metadata checks.
|
|
30
|
+
- Packaged runnable API and CI/CD examples with regression coverage against the installed distribution.
|
|
31
|
+
- Conventional `help`, `-h`, and `--help` CLI entry points with installed-package smoke coverage.
|
|
32
|
+
- Tracked Bumpster configuration, synchronized `VERSION` metadata, and a pre-bump release gate.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- Updated `js-yaml` to 4.3.1 to address merge-key, alias, and ordered-map denial-of-service advisories; refreshed ESLint 9 tooling within the existing major version.
|
|
36
|
+
- Package builds now exclude ignored local dotfiles, and tarball smoke checks recursively for local workflow artifacts.
|
|
37
|
+
- CI now covers supported Node.js releases on Linux and macOS, uses current Node 24-based GitHub Actions, and performs a tag-gated package preflight.
|
|
38
|
+
- Development and dependency updates now target `dev`; Bumpster releases atomically synchronize `dev`, `main`, and `vX.Y.Z` tags.
|
|
39
|
+
- `encryptValue`, `processConfig`, and `yamlock encrypt` now write authenticated v2 payloads by default.
|
|
40
|
+
- `decryptValue` and `processConfig` continue to auto-detect and read both legacy and v2 payloads; API callers can request legacy writing explicitly with `formatVersion: 1` or legacy algorithm options.
|
|
41
|
+
- Repeated `processConfig`/CLI encryption now authenticates and preserves existing payloads instead of adding another encryption layer; fully encrypted in-place inputs are not rewritten.
|
|
42
|
+
- CLI argument parsing now rejects unknown or duplicate options, missing values, extra positional arguments, and options that do not belong to the selected command before reading input files; `keygen --length` accepts only integers from 1 to 4096 bytes.
|
|
43
|
+
- CLI integration tests now force an explicit source mode through the real `bin/yamlock` launcher, preventing stale local `dist` output from masking source changes while preserving distribution-first behavior for normal runs.
|
|
44
|
+
- `processConfig` now validates non-string policies, path options, serializer output, collisions, and circular structures; opaque values are preserved by `ignore`, while `stringify` fails closed for values that cannot be converted without type loss.
|
|
45
|
+
- Selective processing applies non-string policies only to selected leaves, and YAML timestamp values are no longer collapsed into empty objects during CLI traversal.
|
|
46
|
+
- Normal CLI encrypt/decrypt writes now use the shared atomic temporary-file writer, verify that the source did not change after reading, preserve source or existing-output modes, reject symbolic-link paths, and clean up temporary files after failures.
|
|
47
|
+
- CLI help now warns that YAML presentation details are normalized during writes and points users toward `--dry-run` or `--output` workflows.
|
|
48
|
+
- YAML and JSON parse failures now report sanitized diagnostics without echoing source lines that may contain secrets.
|
|
49
|
+
- Default field paths now escape reserved characters in object keys, CLI path lists understand escaped commas, and existing payloads with legacy ambiguous paths remain readable.
|
|
50
|
+
- Config processing and migration now preserve sparse-array length and holes; `processConfig` also retains null prototypes and own keys such as `__proto__` without prototype assignment.
|
|
51
|
+
|
|
52
|
+
## [0.3.0] - 2025-12-01
|
|
53
|
+
### Added
|
|
54
|
+
- `processConfig` non-string policies (`ignore`, `stringify`, `error`) and optional `pathSerializer` hook.
|
|
55
|
+
- Unit tests demonstrating the new policies.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
- README highlights non-string handling in the advanced usage section.
|
|
59
|
+
|
|
60
|
+
## [0.2.10] - 2025-12-01
|
|
61
|
+
### Added
|
|
62
|
+
- README example demonstrating the `--dry-run` workflow (with sample diff output).
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
- Package version bumped to keep documentation in sync.
|
|
66
|
+
|
|
67
|
+
## [0.2.9] - 2025-12-01
|
|
68
|
+
### Added
|
|
69
|
+
- CLI `--dry-run` flag prints the diff without touching files.
|
|
70
|
+
- Structured error codes (e.g., `[yamlock:ERR_MISSING_KEY]`) for machine-readable failure handling.
|
|
71
|
+
- Integration tests covering dry-run and the new error format.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
- README documents the `--dry-run` flag and behavior.
|
|
75
|
+
|
|
76
|
+
## [0.2.8] - 2025-12-01
|
|
77
|
+
### Added
|
|
78
|
+
- Advanced usage docs: selective path tips, CI/CD workflow example (`examples/docs/ci-cd.md`), and an expanded key-rotation guide with scripting advice.
|
|
79
|
+
- README now links to the new docs and highlights selective encryption in a dedicated section.
|
|
80
|
+
|
|
81
|
+
### Changed
|
|
82
|
+
- Package version bumped to keep documentation in sync.
|
|
83
|
+
|
|
84
|
+
## [0.2.7] - 2025-12-01
|
|
85
|
+
### Added
|
|
86
|
+
- README badges for npm version and Node test status, plus a release information section summarizing install options.
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
- Package version bumped to keep documentation in sync.
|
|
90
|
+
|
|
91
|
+
## [0.2.6] - 2025-11-28
|
|
92
|
+
### Added
|
|
93
|
+
- Unit tests covering `processConfig` path filters and CLI integration tests for custom algorithms/decrypt paths.
|
|
94
|
+
|
|
95
|
+
### Changed
|
|
96
|
+
- README/CLI behavior unchanged.
|
|
97
|
+
|
|
98
|
+
## [0.2.5] - 2025-11-28
|
|
99
|
+
### Added
|
|
100
|
+
- `yamlock algorithms` now distinguishes between tested presets and other OpenSSL-provided ciphers.
|
|
101
|
+
- Integration tests updated to confirm the new output format.
|
|
102
|
+
|
|
103
|
+
### Changed
|
|
104
|
+
- README notes that the algorithms command shows both tested and additional cipher lists.
|
|
105
|
+
|
|
106
|
+
## [0.2.4] - 2025-11-28
|
|
107
|
+
### Added
|
|
108
|
+
- `yamlock keygen` command generates random keys with configurable length and format, including guidance on storing them.
|
|
109
|
+
- Integration tests covering key generation.
|
|
110
|
+
|
|
111
|
+
### Changed
|
|
112
|
+
- README documents the key generator usage.
|
|
113
|
+
|
|
114
|
+
## [0.2.3] - 2025-11-27
|
|
115
|
+
### Added
|
|
116
|
+
- `yamlock version` command prints the installed CLI version.
|
|
117
|
+
- `yamlock algorithms` lists all ciphers available in the current runtime.
|
|
118
|
+
- Integration tests cover the new commands.
|
|
119
|
+
|
|
120
|
+
### Changed
|
|
121
|
+
- CLI help and README highlight the new commands.
|
|
122
|
+
- Moved shared fixtures under `fixtures/` so the test runner skips them as standalone suites.
|
|
123
|
+
|
|
124
|
+
## [0.2.2] - 2025-11-27
|
|
125
|
+
### Added
|
|
126
|
+
- CLI support for `--paths` (partial encryption/decryption) and `--output` (separate destination files).
|
|
127
|
+
- `processConfig` now accepts targeted path lists so API/CLI share the same behavior.
|
|
128
|
+
|
|
129
|
+
### Changed
|
|
130
|
+
- README documents the new CLI options with examples.
|
|
131
|
+
|
|
132
|
+
## [0.2.1] - 2025-11-26
|
|
133
|
+
### Changed
|
|
134
|
+
- Unified unit tests around shared fixtures/helpers to remove duplication and ensure every supported algorithm is exercised consistently.
|
|
135
|
+
|
|
136
|
+
## [0.2.0] - 2025-11-26
|
|
137
|
+
### Added
|
|
138
|
+
- Algorithm presets with override support (e.g., `chacha20-poly1305` with auth tags, configurable key/IV lengths).
|
|
139
|
+
- API support for passing algorithm options through `encryptValue`, `decryptValue`, and `processConfig`.
|
|
140
|
+
- Additional unit coverage for presets and algorithm-aware config processing.
|
|
141
|
+
|
|
142
|
+
## [0.1.2] - 2025-11-26
|
|
143
|
+
### Fixed
|
|
144
|
+
- CLI binary now falls back to the source entry during development, ensuring `yamlock` works when installed globally or run via `node bin/yamlock`.
|
|
145
|
+
- Integration tests call the published bin, catching future regressions.
|
|
146
|
+
|
|
147
|
+
## [0.1.1] - 2025-11-26
|
|
148
|
+
### Added
|
|
149
|
+
- Key rotation guide under `docs/key-rotation.md` and README link.
|
|
150
|
+
- Documented CLI exit codes and reference to the example script.
|
|
151
|
+
|
|
152
|
+
## [0.1.0] - 2025-11-26
|
|
153
|
+
### Added
|
|
154
|
+
- Example script under `examples/basic.js` demonstrating encrypt/decrypt flows.
|
|
155
|
+
- GitHub Actions CI workflow to lint, test, and build on pushes/PRs.
|
|
156
|
+
- README now links to the example script.
|
|
157
|
+
|
|
158
|
+
## [0.0.10] - 2025-11-26
|
|
159
|
+
### Added
|
|
160
|
+
- Expanded README with usage examples, encrypted format description, and contributing link.
|
|
161
|
+
- CONTRIBUTING guide plus GitHub issue and pull request templates.
|
|
162
|
+
|
|
163
|
+
## [0.0.9] - 2025-11-26
|
|
164
|
+
### Added
|
|
165
|
+
- CLI now auto-detects YAML vs JSON, loads via js-yaml, and writes back preserving the original format.
|
|
166
|
+
- Integration tests covering YAML encrypt/decrypt flows.
|
|
167
|
+
|
|
168
|
+
## [0.0.8] - 2025-11-26
|
|
169
|
+
### Added
|
|
170
|
+
- CLI skeleton capable of encrypting/decrypting JSON files with `--key` and `--algorithm` options.
|
|
171
|
+
- Integration tests covering CLI encrypt/decrypt flows and key validation.
|
|
172
|
+
|
|
173
|
+
## [0.0.7] - 2025-11-26
|
|
174
|
+
### Added
|
|
175
|
+
- Public API exports for `encryptValue`, `decryptValue`, `processConfig`, and `getSupportedAlgorithms`.
|
|
176
|
+
- Unit tests ensuring the API surface is available and round-trips data correctly.
|
|
177
|
+
|
|
178
|
+
## [0.0.6] - 2025-11-25
|
|
179
|
+
### Added
|
|
180
|
+
- `processConfig` helper to walk objects/arrays and encrypt or decrypt every string value based on field paths.
|
|
181
|
+
- Unit tests covering nested traversal, arrays, round-trips, and validation errors.
|
|
182
|
+
|
|
183
|
+
## [0.0.5] - 2025-11-25
|
|
184
|
+
### Added
|
|
185
|
+
- Field path utilities that serialize nested object/array locations (`src/utils/path.js`).
|
|
186
|
+
- Unit tests covering dot/bracket formatting and invalid input handling.
|
|
187
|
+
|
|
188
|
+
## [0.0.4] - 2025-11-25
|
|
189
|
+
### Added
|
|
190
|
+
- `decryptValue` support with field-path validation and yamlock payload parsing.
|
|
191
|
+
- Unit tests for decrypt flow, mismatch handling, and invalid payload detection.
|
|
192
|
+
|
|
193
|
+
## [0.0.3] - 2025-11-24
|
|
194
|
+
### Added
|
|
195
|
+
- `encryptValue` function that encrypts string fields with per-path salts and random IVs.
|
|
196
|
+
- Unit tests covering payload structure, IV randomness, and validation checks.
|
|
197
|
+
|
|
198
|
+
## [0.0.2] - 2025-11-24
|
|
199
|
+
### Added
|
|
200
|
+
- Crypto utility helpers for key derivation, IV generation, salt encoding, and payload formatting/parsing.
|
|
201
|
+
- Unit tests covering the utility layer and yamlock payload parsing.
|
|
202
|
+
|
|
203
|
+
## [0.0.1] - 2025-11-24
|
|
204
|
+
### Added
|
|
205
|
+
- Initial project scaffold: package metadata, README baseline, ESLint config, CLI stub, and directory structure.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
```
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/yamlock)
|
|
8
|
-
[](https://github.com/phoenixweiss/yamlock/actions/workflows/ci.yml)
|
|
9
9
|
|
|
10
10
|
# yamlock
|
|
11
11
|
|
|
@@ -13,8 +13,10 @@ Value-level encryption for YAML and JSON configuration files. The name **yamlock
|
|
|
13
13
|
|
|
14
14
|
## Requirements
|
|
15
15
|
|
|
16
|
-
- Node.js 22
|
|
17
|
-
|
|
16
|
+
- Node.js 22 or newer
|
|
17
|
+
|
|
18
|
+
The package works with npm or another Node.js package manager. This repository
|
|
19
|
+
uses Yarn Classic 1.22.22 for development and release checks.
|
|
18
20
|
|
|
19
21
|
## Installation
|
|
20
22
|
|
|
@@ -34,9 +36,11 @@ yarn add yamlock # project dependency
|
|
|
34
36
|
|
|
35
37
|
## Features
|
|
36
38
|
|
|
37
|
-
- Encrypt/decrypt individual
|
|
39
|
+
- Encrypt/decrypt individual values with authenticated field-path metadata.
|
|
38
40
|
- CLI workflow that processes YAML or JSON files in place.
|
|
41
|
+
- Safe CLI migration from legacy payloads to authenticated v2 payloads.
|
|
39
42
|
- Recursively lock/unlock entire objects via `processConfig`.
|
|
43
|
+
- Select repeated fields or subtrees with structural path patterns.
|
|
40
44
|
- Public API exports that mirror CLI behavior for programmatic use.
|
|
41
45
|
- Focus on Node.js 22+, ESM modules, and a lightweight dependency set (`js-yaml`).
|
|
42
46
|
|
|
@@ -48,13 +52,17 @@ yarn add yamlock # project dependency
|
|
|
48
52
|
# Encrypt values in a YAML file
|
|
49
53
|
YAMLOCK_KEY="super-secret" yamlock encrypt config.yaml
|
|
50
54
|
|
|
51
|
-
# Decrypt values in place
|
|
52
|
-
yamlock decrypt settings.json --key "super-secret"
|
|
55
|
+
# Decrypt values in place; the payload determines its format and algorithm
|
|
56
|
+
yamlock decrypt settings.json --key "super-secret"
|
|
53
57
|
|
|
54
58
|
# Encrypt only selected fields into a new file
|
|
55
59
|
yamlock encrypt config.json --key "$YAMLOCK_KEY" --paths "db.password,api.token" --output config.secure.json
|
|
56
60
|
|
|
61
|
+
# Encrypt repeated tokens and an entire subtree
|
|
62
|
+
yamlock encrypt config.json --key "$YAMLOCK_KEY" --path-patterns 'users[*].token,services.**'
|
|
63
|
+
|
|
57
64
|
# Inspect CLI metadata
|
|
65
|
+
yamlock --help
|
|
58
66
|
yamlock version
|
|
59
67
|
yamlock algorithms
|
|
60
68
|
|
|
@@ -63,39 +71,170 @@ yamlock keygen --length 64 --format base64
|
|
|
63
71
|
|
|
64
72
|
# Preview changes without touching files
|
|
65
73
|
yamlock encrypt config.yml -o config.enc.yml -p db.password -k "my-secret-key" -d
|
|
74
|
+
|
|
75
|
+
# Preview a legacy-to-v2 migration without printing config contents
|
|
76
|
+
yamlock migrate config.yml -k "$YAMLOCK_KEY" -p db.password -d
|
|
77
|
+
|
|
78
|
+
# Explicitly write legacy v1 for a temporary compatibility requirement
|
|
79
|
+
yamlock encrypt config.yml -k "$YAMLOCK_KEY" --legacy --algorithm aes-256-cbc
|
|
66
80
|
```
|
|
67
81
|
|
|
68
|
-
The CLI detects YAML (`.yaml`/`.yml`) and JSON extensions automatically and
|
|
82
|
+
The CLI detects YAML (`.yaml`/`.yml`) and JSON extensions automatically and
|
|
83
|
+
writes the file back in the same format. Normal `encrypt` and `decrypt` writes
|
|
84
|
+
use an fsynced temporary file plus an atomic rename. In-place writes preserve
|
|
85
|
+
the source mode; a new `--output` inherits the source mode, while an existing
|
|
86
|
+
regular output preserves its own mode. Mutating operations reject symbolic-link
|
|
87
|
+
inputs and outputs rather than following or replacing their targets.
|
|
88
|
+
|
|
89
|
+
YAML writes preserve parsed values, not the original syntax tree. Comments,
|
|
90
|
+
anchor/alias syntax, merge keys, explicit tags, quoting, and formatting may be
|
|
91
|
+
removed or normalized. Review [the YAML rewrite contract](docs/yaml-behavior.md)
|
|
92
|
+
and use `--dry-run` or `--output` when presentation details matter.
|
|
69
93
|
|
|
70
94
|
Options of note:
|
|
71
95
|
- `--output <file>` writes the result to a separate file instead of overwriting the input.
|
|
72
|
-
- `--paths <path1,path2>` targets only the specified fields
|
|
73
|
-
- `--
|
|
74
|
-
-
|
|
75
|
-
-
|
|
96
|
+
- `--paths <path1,path2>` targets only the specified fields using the [escaped path syntax](#field-path-syntax).
|
|
97
|
+
- `--path-patterns <pattern1,pattern2>` selects structural paths with whole-segment `*`, `[*]`, and `**` wildcards.
|
|
98
|
+
- `--dry-run` previews an operation without modifying files; encrypt/decrypt print content changes, while migrate prints only counts and target paths.
|
|
99
|
+
- `migrate` decrypts selected legacy payloads and re-encrypts them as authenticated v2 payloads.
|
|
100
|
+
- `migrate --allow-mixed` additionally authenticates and preserves selected values that are already v2.
|
|
101
|
+
- In-place migration creates `<file>.yamlock.bak` by default; `--no-backup` disables it explicitly.
|
|
102
|
+
- `encrypt --legacy` writes the legacy v1 format; `--algorithm` is accepted for encryption only together with `--legacy`.
|
|
103
|
+
- `encrypt --error-on-encrypted` fails when a selected value is already encrypted instead of preserving it.
|
|
104
|
+
- `encrypt --force-encrypt` explicitly treats selected `yl|...` strings as plaintext and adds another encryption layer.
|
|
105
|
+
- Command `keygen` produces a random key of 1–4096 whole bytes and shows how to store it (shell export or `.env`).
|
|
106
|
+
- Command `algorithms` prints the fixed v2 profile, tested legacy presets, and additional legacy ciphers available from the runtime.
|
|
76
107
|
- Command `version` prints the installed CLI version.
|
|
77
108
|
|
|
109
|
+
The CLI rejects unknown or duplicate options, missing option values, extra
|
|
110
|
+
positional arguments, and options that do not apply to the selected command.
|
|
111
|
+
Argument errors use structured `[yamlock:ERR_*]` codes and are reported before
|
|
112
|
+
the CLI reads or modifies a configuration file.
|
|
113
|
+
|
|
114
|
+
### Field path syntax
|
|
115
|
+
|
|
116
|
+
Default field paths use dots for object nesting and brackets for array indexes:
|
|
117
|
+
`db.password` and `users[0].token`. Inside an object key, backslashes, dots,
|
|
118
|
+
brackets, and commas are escaped with a backslash. For example,
|
|
119
|
+
`db\.primary.token` selects `token` below the literal key `db.primary`, while
|
|
120
|
+
`db.primary.token` selects three nested object keys. Quote escaped CLI paths so
|
|
121
|
+
the shell passes each backslash unchanged:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
yamlock encrypt config.json --key "$YAMLOCK_KEY" --paths 'db\.primary.token,labels\,primary'
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Node.js callers can build the same canonical strings from unambiguous segments:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
import { processConfig, serializePath } from 'yamlock';
|
|
131
|
+
|
|
132
|
+
const selectedPath = serializePath(['db.primary', 'token']);
|
|
133
|
+
const encrypted = processConfig(config, {
|
|
134
|
+
mode: 'encrypt',
|
|
135
|
+
key: process.env.YAMLOCK_KEY,
|
|
136
|
+
paths: [selectedPath]
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
New payloads bind authentication to the escaped canonical path. Payloads written
|
|
141
|
+
by older yamlock versions for keys containing reserved characters remain
|
|
142
|
+
readable through the default serializer's compatibility path; selecting those
|
|
143
|
+
keys now requires the canonical escaped spelling. A custom `pathSerializer`
|
|
144
|
+
keeps its own contract and does not use the default compatibility fallback.
|
|
145
|
+
|
|
146
|
+
### Path patterns
|
|
147
|
+
|
|
148
|
+
Patterns are separate from exact `paths`; using both forms a union. They match
|
|
149
|
+
complete structural leaf paths:
|
|
150
|
+
|
|
151
|
+
- `services.*.token` matches one object-key segment, such as
|
|
152
|
+
`services.api.token`, but not `services[0].token`.
|
|
153
|
+
- `users[*].token` matches array elements such as `users[0].token`.
|
|
154
|
+
- `db.**` matches a leaf at `db` or any descendant below it, including arrays.
|
|
155
|
+
|
|
156
|
+
Wildcards must occupy a complete segment. Partial globs such as `service-*`
|
|
157
|
+
are rejected with `ERR_INVALID_PATH_PATTERNS` before the CLI reads the input
|
|
158
|
+
file. Literal reserved characters use the same escaping as exact paths, and
|
|
159
|
+
`\*` selects a literal asterisk key:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
yamlock encrypt config.json \
|
|
163
|
+
--key "$YAMLOCK_KEY" \
|
|
164
|
+
--paths 'root.literal' \
|
|
165
|
+
--path-patterns 'services.*.token,users[*].token,labels\,primary'
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Patterns only decide which leaves are selected. Encryption, decryption, and
|
|
169
|
+
migration continue to bind every payload to its exact canonical leaf path.
|
|
170
|
+
Node.js callers use `pathPatterns: string[]`; it cannot be combined with a
|
|
171
|
+
custom `pathSerializer`.
|
|
172
|
+
|
|
78
173
|
### Node.js API
|
|
79
174
|
|
|
80
175
|
```js
|
|
81
|
-
import { encryptValue, decryptValue, processConfig } from 'yamlock';
|
|
176
|
+
import { encryptValue, decryptValue, processConfig, serializePath } from 'yamlock';
|
|
82
177
|
|
|
83
178
|
const encrypted = encryptValue('swordfish', process.env.YAMLOCK_KEY, 'db.password');
|
|
84
179
|
const decrypted = decryptValue(encrypted, process.env.YAMLOCK_KEY, 'db.password');
|
|
85
180
|
|
|
86
181
|
const config = { db: { password: 'swordfish' } };
|
|
87
|
-
const
|
|
88
|
-
const
|
|
182
|
+
const selectors = { pathPatterns: ['db.**'] };
|
|
183
|
+
const locked = processConfig(config, {
|
|
184
|
+
mode: 'encrypt',
|
|
185
|
+
key: process.env.YAMLOCK_KEY,
|
|
186
|
+
...selectors
|
|
187
|
+
});
|
|
188
|
+
const unlocked = processConfig(locked, {
|
|
189
|
+
mode: 'decrypt',
|
|
190
|
+
key: process.env.YAMLOCK_KEY,
|
|
191
|
+
...selectors
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Expected Node.js API failures extend `YamlockError` and expose stable `ERR_*`
|
|
196
|
+
codes. Specialized classes distinguish validation, payload, authentication,
|
|
197
|
+
legacy decryption, and config-processing failures; `YAMLOCK_ERROR_CODES` avoids
|
|
198
|
+
repeating code strings. See the [Node.js error contract](docs/errors.md).
|
|
199
|
+
|
|
200
|
+
The package includes TypeScript declarations. The supported exports, option
|
|
201
|
+
types, and `1.x` compatibility guarantees are listed in the
|
|
202
|
+
[public Node.js API contract](docs/api.md).
|
|
203
|
+
|
|
204
|
+
Repeated encryption is safe by default. `processConfig` authenticates selected
|
|
205
|
+
existing payloads with the supplied key and field path, preserves them
|
|
206
|
+
unchanged, and encrypts only selected plaintext values. Use
|
|
207
|
+
`existingPayloadPolicy: 'error'` when existing encrypted values should fail the
|
|
208
|
+
operation:
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
processConfig(config, {
|
|
212
|
+
mode: 'encrypt',
|
|
213
|
+
key: process.env.YAMLOCK_KEY,
|
|
214
|
+
existingPayloadPolicy: 'error'
|
|
215
|
+
});
|
|
89
216
|
```
|
|
90
217
|
|
|
218
|
+
Malformed payloads, incorrect keys, and incorrect field paths are never silently
|
|
219
|
+
skipped. Re-running `yamlock encrypt` on a fully encrypted input does not rewrite
|
|
220
|
+
the source file. Use `yamlock migrate` rather than `encrypt` to convert preserved
|
|
221
|
+
legacy values to v2.
|
|
222
|
+
|
|
223
|
+
If plaintext intentionally begins with `yl|`, use
|
|
224
|
+
`existingPayloadPolicy: 'encrypt'` or CLI `--force-encrypt`. This also permits
|
|
225
|
+
deliberate nested encryption, so it should not be enabled in routine workflows;
|
|
226
|
+
each added layer requires a matching decrypt operation.
|
|
227
|
+
|
|
91
228
|
See `examples/basic.js` for a runnable end-to-end script (`node examples/basic.js`).
|
|
92
229
|
|
|
93
|
-
###
|
|
230
|
+
### Legacy algorithm customization
|
|
94
231
|
|
|
95
|
-
|
|
232
|
+
V2 deliberately has no free-form cipher settings. For temporary legacy
|
|
233
|
+
compatibility, select format version 1 and provide the old cipher options:
|
|
96
234
|
|
|
97
235
|
```js
|
|
98
236
|
const encrypted = encryptValue('swordfish', KEY, 'db.password', {
|
|
237
|
+
formatVersion: 1,
|
|
99
238
|
algorithm: 'chacha20-poly1305',
|
|
100
239
|
ivLength: 12 // override the IV size used during encryption
|
|
101
240
|
});
|
|
@@ -110,6 +249,7 @@ const processed = processConfig(
|
|
|
110
249
|
{
|
|
111
250
|
mode: 'encrypt',
|
|
112
251
|
key: KEY,
|
|
252
|
+
formatVersion: 1,
|
|
113
253
|
algorithm: { algorithm: 'aes-192-cbc', ivLength: 24 }
|
|
114
254
|
}
|
|
115
255
|
);
|
|
@@ -118,6 +258,7 @@ const processed = processConfig(
|
|
|
118
258
|
const restored = processConfig(processed, {
|
|
119
259
|
mode: 'decrypt',
|
|
120
260
|
key: KEY,
|
|
261
|
+
formatVersion: 1,
|
|
121
262
|
algorithm: { algorithm: 'aes-192-cbc', ivLength: 24 }
|
|
122
263
|
});
|
|
123
264
|
|
|
@@ -126,7 +267,7 @@ const mixedConfig = { db: { password: 'secret', retries: 3 } };
|
|
|
126
267
|
const lockedMixed = processConfig(mixedConfig, {
|
|
127
268
|
mode: 'encrypt',
|
|
128
269
|
key: KEY,
|
|
129
|
-
nonStringPolicy: 'stringify', // stringifies numbers
|
|
270
|
+
nonStringPolicy: 'stringify', // stringifies finite numbers, booleans, and null
|
|
130
271
|
pathSerializer: (segments) => segments.join('/') // custom path naming (db/password instead of dot notation)
|
|
131
272
|
});
|
|
132
273
|
|
|
@@ -146,49 +287,135 @@ const lockedUsers = processConfig(
|
|
|
146
287
|
);
|
|
147
288
|
```
|
|
148
289
|
|
|
290
|
+
`processConfig` recursively traverses arrays and plain objects. With the default
|
|
291
|
+
`nonStringPolicy: 'ignore'`, selected non-string and opaque values such as
|
|
292
|
+
`Date`, `undefined`, or `BigInt` are preserved unchanged. Policy `'error'`
|
|
293
|
+
rejects a selected non-string value. Policy `'stringify'` accepts only finite
|
|
294
|
+
numbers, booleans, and `null`; values that native JSON conversion could omit or
|
|
295
|
+
silently change are rejected instead of being encrypted with lost type
|
|
296
|
+
information. Decrypted stringified primitives remain strings.
|
|
297
|
+
|
|
298
|
+
Circular arrays/objects are rejected. A custom `pathSerializer` must return a
|
|
299
|
+
non-empty, unique string for every leaf and the same serializer must be used for
|
|
300
|
+
encryption and decryption. Policies apply only to values selected by `paths`;
|
|
301
|
+
unselected leaf values are preserved.
|
|
302
|
+
|
|
303
|
+
Direct `processConfig` calls preserve empty containers, sparse-array length and
|
|
304
|
+
holes, null-prototype objects, and own keys such as `__proto__`. JSON and YAML
|
|
305
|
+
files retain the values their parsers can represent, but those formats do not
|
|
306
|
+
encode JavaScript sparse-array holes as a distinct portable value.
|
|
307
|
+
|
|
308
|
+
### Authenticated payload v2 (default)
|
|
309
|
+
|
|
310
|
+
`encryptValue`, `processConfig`, and `yamlock encrypt` write authenticated v2
|
|
311
|
+
payloads by default. `decryptValue` and `processConfig` automatically read both
|
|
312
|
+
v1 and v2.
|
|
313
|
+
|
|
314
|
+
```js
|
|
315
|
+
const encrypted = encryptValue('swordfish', KEY, 'db.password');
|
|
316
|
+
|
|
317
|
+
const locked = processConfig(
|
|
318
|
+
{ db: { password: 'swordfish' } },
|
|
319
|
+
{ mode: 'encrypt', key: KEY }
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
// No format or algorithm option is required when decrypting.
|
|
323
|
+
const original = decryptValue(encrypted, KEY, 'db.password');
|
|
324
|
+
const unlocked = processConfig(locked, { mode: 'decrypt', key: KEY });
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
V2 uses a fixed AES-256-GCM profile, a random 12-byte nonce, a 16-byte
|
|
328
|
+
authentication tag, and scrypt with a separate random KDF salt. The field path
|
|
329
|
+
and security-critical metadata are authenticated. Free-form cipher and size
|
|
330
|
+
overrides are intentionally unavailable for v2.
|
|
331
|
+
|
|
332
|
+
Existing legacy files can be migrated safely with the CLI:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
# Preview counts and target paths; config contents are not printed.
|
|
336
|
+
yamlock migrate config.yaml --key "$YAMLOCK_KEY" --paths "db.password,api.token" --dry-run
|
|
337
|
+
|
|
338
|
+
# Migrate in place and create config.yaml.yamlock.bak.
|
|
339
|
+
yamlock migrate config.yaml --key "$YAMLOCK_KEY" --paths "db.password,api.token"
|
|
340
|
+
|
|
341
|
+
# Preserve the source and write a new file. Existing outputs are never replaced.
|
|
342
|
+
yamlock migrate config.yaml --key "$YAMLOCK_KEY" --paths "db.password,api.token" --output config.v2.yaml
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Migration validates every selected value and builds the complete result before
|
|
346
|
+
writing. Selected plaintext and non-string values are rejected, so use
|
|
347
|
+
`--paths` or `--path-patterns` for partially encrypted configs. Selected v2
|
|
348
|
+
values are rejected unless `--allow-mixed` is set; with that flag they are
|
|
349
|
+
authenticated and kept unchanged. In-place writes are atomic, preserve the
|
|
350
|
+
source file mode, and do not replace an existing backup. To roll back, verify
|
|
351
|
+
the backup and then copy
|
|
352
|
+
`config.yaml.yamlock.bak` over `config.yaml`.
|
|
353
|
+
|
|
354
|
+
Legacy AES-CBC payloads have no authentication, so migration can only validate
|
|
355
|
+
their structure, field path, and successful decryption. Backups use the source
|
|
356
|
+
file permissions and match `*.yamlock.bak` in the repository `.gitignore`.
|
|
357
|
+
See [the payload v2 design](docs/design/payload-v2.md) for the format, threat
|
|
358
|
+
model, limits, and staged migration plan. This design and implementation have
|
|
359
|
+
not received a third-party security audit.
|
|
360
|
+
|
|
149
361
|
## Advanced usage
|
|
150
362
|
|
|
151
|
-
- **Selective encryption**:
|
|
152
|
-
- **
|
|
363
|
+
- **Selective encryption**: use exact `--paths`/`paths` selectors, structural `--path-patterns`/`pathPatterns`, or their union to encrypt only sensitive fields.
|
|
364
|
+
- **Repeated encryption**: valid selected payloads are authenticated and preserved; add `--error-on-encrypted` or `existingPayloadPolicy: 'error'` for strict workflows.
|
|
365
|
+
- **Non-string handling**: use `nonStringPolicy: 'ignore' | 'stringify' | 'error'` to preserve opaque leaves, stringify finite JSON primitives, or reject selected non-string values; use `pathSerializer` to change path representation (e.g., `db/password` instead of dot notation).
|
|
153
366
|
- **CI/CD flows**: see [examples/docs/ci-cd.md](examples/docs/ci-cd.md) for a GitHub Actions job that decrypts configs for builds and re-encrypts them before publishing artifacts.
|
|
154
367
|
- **Key rotation**: follow [examples/docs/key-rotation.md](examples/docs/key-rotation.md) for a step-by-step process, including scripting tips for large repos.
|
|
155
368
|
|
|
156
369
|
### Supported algorithms
|
|
157
370
|
|
|
158
|
-
| Algorithm |
|
|
159
|
-
|
|
160
|
-
| `aes-
|
|
161
|
-
| `aes-
|
|
162
|
-
| `aes-
|
|
163
|
-
| `
|
|
371
|
+
| Algorithm | Format | Notes |
|
|
372
|
+
|-----------|--------|-------|
|
|
373
|
+
| `aes-256-gcm` | v2 default | Fixed authenticated profile with scrypt, a 12-byte nonce, and a 16-byte tag. |
|
|
374
|
+
| `aes-128-cbc` | legacy v1 | Compatibility only; ciphertext and metadata are not authenticated. |
|
|
375
|
+
| `aes-192-cbc` | legacy v1 | Compatibility only; ciphertext and metadata are not authenticated. |
|
|
376
|
+
| `aes-256-cbc` | legacy v1 | Compatibility default when `--legacy` is used without `--algorithm`. |
|
|
377
|
+
| `chacha20-poly1305` | legacy v1 | Authenticates ciphertext, but not all serialized metadata protected by v2. |
|
|
164
378
|
|
|
165
|
-
|
|
379
|
+
Additional algorithms exposed by `crypto.getCiphers()` are available only in
|
|
380
|
+
explicit legacy mode and are not part of the supported v2 profile. Prefer the
|
|
381
|
+
default v2 writer for new data.
|
|
166
382
|
|
|
167
383
|
## Release information
|
|
168
384
|
|
|
169
|
-
- The badges at the top show the latest npm version and the status of the
|
|
170
|
-
- See [
|
|
385
|
+
- The badges at the top show the latest npm version and the status of the full CI matrix.
|
|
386
|
+
- See [GitHub Releases](https://github.com/phoenixweiss/yamlock/releases) and
|
|
387
|
+
[CHANGELOG.md](CHANGELOG.md) for detailed release notes; install a specific
|
|
388
|
+
version via `npm install yamlock@<version>`.
|
|
389
|
+
- yamlock versions are bumped with my own release utility,
|
|
390
|
+
[Bumpster](https://github.com/phoenixweiss/Bumpster). It keeps the tracked
|
|
391
|
+
`VERSION` file and `package.json` synchronized while publishing the `dev`,
|
|
392
|
+
`main`, and `vX.Y.Z` Git refs atomically. Each stable tag must pass the full
|
|
393
|
+
CI and package preflight before its GitHub Release is published.
|
|
394
|
+
|
|
395
|
+
### Encrypted value formats
|
|
396
|
+
|
|
397
|
+
New values use the authenticated v2 envelope:
|
|
171
398
|
|
|
172
|
-
|
|
399
|
+
```txt
|
|
400
|
+
yl|2|aes-256-gcm|scrypt|32768|8|1|<kdf_salt>|<nonce>|<path>|<ciphertext>|<tag>
|
|
401
|
+
```
|
|
173
402
|
|
|
174
|
-
|
|
403
|
+
Legacy values remain readable and can still be written explicitly:
|
|
175
404
|
|
|
176
405
|
```txt
|
|
177
406
|
yl|<algorithm>|<salt_base64>|<iv_base64>|<data_base64>
|
|
178
407
|
```
|
|
179
408
|
|
|
180
|
-
|
|
181
|
-
-
|
|
182
|
-
- `<algorithm>` - algorithm name (e.g., aes-256-cbc)
|
|
183
|
-
- `<salt_base64>` - Base64-encoded field path
|
|
184
|
-
- `<iv_base64>` - Base64-encoded initialization vector
|
|
185
|
-
- `<data_base64>` - Base64-encoded encrypted data
|
|
409
|
+
The legacy name `<salt_base64>` is historical: that segment is only the
|
|
410
|
+
Base64-encoded field path and is not a random salt or KDF input.
|
|
186
411
|
|
|
187
|
-
|
|
412
|
+
See [the payload v2 design](docs/design/payload-v2.md) for the canonical field
|
|
413
|
+
definitions, limits, compatibility rules, and legacy security limitations.
|
|
188
414
|
|
|
189
415
|
### Key rotation
|
|
190
416
|
|
|
191
|
-
See [
|
|
417
|
+
See [the key rotation guide](examples/docs/key-rotation.md) for a step-by-step
|
|
418
|
+
workflow that re-encrypts values with v2 and a new `YAMLOCK_KEY`.
|
|
192
419
|
|
|
193
420
|
## Inspiration and motivation
|
|
194
421
|
|
|
@@ -211,9 +438,15 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for development workflow, available scrip
|
|
|
211
438
|
|
|
212
439
|
## Future work
|
|
213
440
|
|
|
214
|
-
-
|
|
215
|
-
|
|
216
|
-
|
|
441
|
+
- Safe subtree and wildcard selectors are specified in the
|
|
442
|
+
[path pattern design proposal](docs/design/path-patterns.md). Existing
|
|
443
|
+
`paths` remain exact; the proposed feature is not implemented yet.
|
|
444
|
+
- An async encryption API with bounded scrypt concurrency for large configs.
|
|
445
|
+
- Stricter file-format validation and preservation rules for advanced YAML features.
|
|
446
|
+
|
|
447
|
+
## Author
|
|
448
|
+
|
|
449
|
+
Created and maintained by [Pavel Tkachev (@phoenixweiss)](https://github.com/phoenixweiss).
|
|
217
450
|
|
|
218
451
|
## License
|
|
219
452
|
|
package/bin/yamlock
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
async function loadCliEntry() {
|
|
4
|
+
if (process.env.YAMLOCK_TEST_SOURCE === '1') {
|
|
5
|
+
const sourceModule = await import('../src/cli/cli.js');
|
|
6
|
+
if (sourceModule.runCli) {
|
|
7
|
+
return sourceModule.runCli;
|
|
8
|
+
}
|
|
9
|
+
throw new Error('Source CLI entry does not export runCli.');
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
try {
|
|
5
13
|
const distModule = await import('../dist/cli/cli.js');
|
|
6
14
|
if (distModule.runCli) {
|