yamlock 0.2.9 → 0.3.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.md +28 -0
- package/dist/utils/config.js +20 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,6 +60,9 @@ yamlock algorithms
|
|
|
60
60
|
|
|
61
61
|
# Generate a random key for YAMLOCK_KEY
|
|
62
62
|
yamlock keygen --length 64 --format base64
|
|
63
|
+
|
|
64
|
+
# Preview changes without touching files
|
|
65
|
+
yamlock encrypt config.yml -o config.enc.yml -p db.password -k "my-secret-key" -d
|
|
63
66
|
```
|
|
64
67
|
|
|
65
68
|
The CLI detects YAML (`.yaml`/`.yml`) and JSON extensions automatically and writes the file back in the same format.
|
|
@@ -117,11 +120,36 @@ const restored = processConfig(processed, {
|
|
|
117
120
|
key: KEY,
|
|
118
121
|
algorithm: { algorithm: 'aes-192-cbc', ivLength: 24 }
|
|
119
122
|
});
|
|
123
|
+
|
|
124
|
+
// Control what happens when encountering non-string values and customize path IDs
|
|
125
|
+
const mixedConfig = { db: { password: 'secret', retries: 3 } };
|
|
126
|
+
const lockedMixed = processConfig(mixedConfig, {
|
|
127
|
+
mode: 'encrypt',
|
|
128
|
+
key: KEY,
|
|
129
|
+
nonStringPolicy: 'stringify', // stringifies numbers/objects before encrypting
|
|
130
|
+
pathSerializer: (segments) => segments.join('/') // custom path naming (db/password instead of dot notation)
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Example of a path serializer that includes array indexes explicitly
|
|
134
|
+
const lockedUsers = processConfig(
|
|
135
|
+
{ users: [{ tokens: ['abc'] }] },
|
|
136
|
+
{
|
|
137
|
+
mode: 'encrypt',
|
|
138
|
+
key: KEY,
|
|
139
|
+
pathSerializer: (segments) =>
|
|
140
|
+
segments
|
|
141
|
+
.map((segment, index) =>
|
|
142
|
+
typeof segment === 'number' ? `[${segment}]` : index === 0 ? segment : `/${segment}`
|
|
143
|
+
)
|
|
144
|
+
.join('')
|
|
145
|
+
}
|
|
146
|
+
);
|
|
120
147
|
```
|
|
121
148
|
|
|
122
149
|
## Advanced usage
|
|
123
150
|
|
|
124
151
|
- **Selective encryption**: combine `--paths` on the CLI or `paths: []` in `processConfig` to encrypt only sensitive sections of a config file.
|
|
152
|
+
- **Non-string handling**: use `nonStringPolicy: 'ignore' | 'stringify' | 'error'` to control how numbers/objects are treated, and `pathSerializer` to change how traversal paths are represented (e.g., `db/password` instead of dot notation).
|
|
125
153
|
- **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.
|
|
126
154
|
- **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.
|
|
127
155
|
|
package/dist/utils/config.js
CHANGED
|
@@ -15,6 +15,8 @@ const MODES = {
|
|
|
15
15
|
* @param {string|Buffer} options.key
|
|
16
16
|
* @param {string|object} [options.algorithm]
|
|
17
17
|
* @param {object} [options.algorithmOptions]
|
|
18
|
+
* @param {"ignore"|"stringify"|"error"} [options.nonStringPolicy]
|
|
19
|
+
* @param {(segments: Array<string|number>) => string} [options.pathSerializer]
|
|
18
20
|
* @param {string[]} [options.paths]
|
|
19
21
|
* @param {Array<string|number>} [options.parentPath]
|
|
20
22
|
* @returns {Object|Array}
|
|
@@ -37,11 +39,13 @@ export function processConfig(node, options) {
|
|
|
37
39
|
...options,
|
|
38
40
|
mode,
|
|
39
41
|
parentPath: options.parentPath ?? [],
|
|
40
|
-
normalizedPaths
|
|
42
|
+
normalizedPaths,
|
|
43
|
+
nonStringPolicy: options.nonStringPolicy ?? 'ignore',
|
|
44
|
+
pathSerializer: options.pathSerializer
|
|
41
45
|
});
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
function traverseConfig(node, { mode, key, algorithm, algorithmOptions, parentPath, normalizedPaths }) {
|
|
48
|
+
function traverseConfig(node, { mode, key, algorithm, algorithmOptions, parentPath, normalizedPaths, nonStringPolicy, pathSerializer }) {
|
|
45
49
|
const isArrayNode = Array.isArray(node);
|
|
46
50
|
const result = isArrayNode ? [] : {};
|
|
47
51
|
const cryptoOptions = algorithmOptions ?? algorithm;
|
|
@@ -49,7 +53,9 @@ function traverseConfig(node, { mode, key, algorithm, algorithmOptions, parentPa
|
|
|
49
53
|
Object.entries(node).forEach(([rawKey, value]) => {
|
|
50
54
|
const segment = isArrayNode ? Number(rawKey) : rawKey;
|
|
51
55
|
const targetKey = isArrayNode ? segment : rawKey;
|
|
52
|
-
const currentPath =
|
|
56
|
+
const currentPath = pathSerializer
|
|
57
|
+
? pathSerializer([...parentPath, segment])
|
|
58
|
+
: buildPath(parentPath, segment);
|
|
53
59
|
|
|
54
60
|
if (value !== null && typeof value === 'object') {
|
|
55
61
|
result[targetKey] = traverseConfig(value, {
|
|
@@ -58,14 +64,22 @@ function traverseConfig(node, { mode, key, algorithm, algorithmOptions, parentPa
|
|
|
58
64
|
algorithm: cryptoOptions,
|
|
59
65
|
algorithmOptions: cryptoOptions,
|
|
60
66
|
parentPath: [...parentPath, segment],
|
|
61
|
-
normalizedPaths
|
|
67
|
+
normalizedPaths,
|
|
68
|
+
nonStringPolicy,
|
|
69
|
+
pathSerializer
|
|
62
70
|
});
|
|
63
71
|
return;
|
|
64
72
|
}
|
|
65
73
|
|
|
66
74
|
if (typeof value !== 'string') {
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
if (nonStringPolicy === 'stringify') {
|
|
76
|
+
value = JSON.stringify(value);
|
|
77
|
+
} else if (nonStringPolicy === 'error') {
|
|
78
|
+
throw new Error(`Non-string value encountered at ${currentPath}`);
|
|
79
|
+
} else {
|
|
80
|
+
result[targetKey] = value;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
69
83
|
}
|
|
70
84
|
|
|
71
85
|
const shouldProcess = !normalizedPaths || normalizedPaths.has(currentPath);
|
package/package.json
CHANGED