@nexus_js/security 0.7.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nexus Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @nexus_js/security
2
+
3
+ **Vault-lite** — in-memory secrets with hot-reload (`nexusVault`, `ctx.secrets`).
4
+
5
+ **Shield-lite** — build manifest helpers (`shield-manifest.json`, action name extraction).
6
+
7
+ Used by `@nexus_js/server` and the CLI; no third-party services.
@@ -0,0 +1,3 @@
1
+ export { NexusVault, nexusVault, getVaultSecretsMap, type VaultListener, } from './vault.js';
2
+ export { SHIELD_MANIFEST_FILENAME, type ShieldManifestV1, parseShieldManifest, loadShieldManifestFromRoot, extractActionNamesFromActionsSource, collectActionNamesFromOutputDir, } from './shield.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,KAAK,aAAa,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,wBAAwB,EACxB,KAAK,gBAAgB,EACrB,mBAAmB,EACnB,0BAA0B,EAC1B,mCAAmC,EACnC,+BAA+B,GAChC,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { NexusVault, nexusVault, getVaultSecretsMap, } from './vault.js';
2
+ export { SHIELD_MANIFEST_FILENAME, parseShieldManifest, loadShieldManifestFromRoot, extractActionNamesFromActionsSource, collectActionNamesFromOutputDir, } from './shield.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,UAAU,EACV,kBAAkB,GAEnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,wBAAwB,EAExB,mBAAmB,EACnB,0BAA0B,EAC1B,mCAAmC,EACnC,+BAA+B,GAChC,MAAM,aAAa,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Nexus Shield-lite — build-time manifest (routes + server action names) for request allowlists.
3
+ */
4
+ export declare const SHIELD_MANIFEST_FILENAME = "shield-manifest.json";
5
+ export interface ShieldManifestV1 {
6
+ version: 1;
7
+ routes: string[];
8
+ actions: string[];
9
+ }
10
+ export declare function parseShieldManifest(raw: string): ShieldManifestV1 | null;
11
+ export declare function loadShieldManifestFromRoot(appRoot: string): ShieldManifestV1 | null;
12
+ export declare function extractActionNamesFromActionsSource(source: string): string[];
13
+ /** Scan .nexus/output for *.actions.js files (recursive). */
14
+ export declare function collectActionNamesFromOutputDir(outDir: string): string[];
15
+ //# sourceMappingURL=shield.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shield.d.ts","sourceRoot":"","sources":["../src/shield.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,eAAO,MAAM,wBAAwB,yBAAyB,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAaxE;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAOnF;AAKD,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ5E;AAsBD,6DAA6D;AAC7D,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAYxE"}
package/dist/shield.js ADDED
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Nexus Shield-lite — build-time manifest (routes + server action names) for request allowlists.
3
+ */
4
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ export const SHIELD_MANIFEST_FILENAME = 'shield-manifest.json';
7
+ export function parseShieldManifest(raw) {
8
+ try {
9
+ const o = JSON.parse(raw);
10
+ if (o.version !== 1)
11
+ return null;
12
+ if (!Array.isArray(o.routes) || !Array.isArray(o.actions))
13
+ return null;
14
+ return {
15
+ version: 1,
16
+ routes: o.routes.filter((x) => typeof x === 'string'),
17
+ actions: o.actions.filter((x) => typeof x === 'string'),
18
+ };
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ export function loadShieldManifestFromRoot(appRoot) {
25
+ const p = join(appRoot, '.nexus', 'output', SHIELD_MANIFEST_FILENAME);
26
+ try {
27
+ return parseShieldManifest(readFileSync(p, 'utf-8'));
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ /** Matches registerAction("name", …) in emitted sidecars (JSON.stringify always uses double quotes). */
34
+ const RE_REGISTER_ACTION = /registerAction\s*\(\s*"([^"]+)"/g;
35
+ export function extractActionNamesFromActionsSource(source) {
36
+ const out = [];
37
+ let m;
38
+ RE_REGISTER_ACTION.lastIndex = 0;
39
+ while ((m = RE_REGISTER_ACTION.exec(source)) !== null) {
40
+ out.push(m[1]);
41
+ }
42
+ return out;
43
+ }
44
+ function collectFilesRecursive(dir, match) {
45
+ const out = [];
46
+ if (!existsSync(dir))
47
+ return out;
48
+ function walk(d) {
49
+ for (const name of readdirSync(d)) {
50
+ const p = join(d, name);
51
+ let st;
52
+ try {
53
+ st = statSync(p);
54
+ }
55
+ catch {
56
+ continue;
57
+ }
58
+ if (st.isDirectory())
59
+ walk(p);
60
+ else if (match(name))
61
+ out.push(p);
62
+ }
63
+ }
64
+ walk(dir);
65
+ return out;
66
+ }
67
+ /** Scan .nexus/output for *.actions.js files (recursive). */
68
+ export function collectActionNamesFromOutputDir(outDir) {
69
+ const names = new Set();
70
+ const files = collectFilesRecursive(outDir, (n) => n.endsWith('.actions.js'));
71
+ for (const p of files) {
72
+ try {
73
+ const src = readFileSync(p, 'utf-8');
74
+ for (const n of extractActionNamesFromActionsSource(src))
75
+ names.add(n);
76
+ }
77
+ catch {
78
+ /* skip */
79
+ }
80
+ }
81
+ return [...names].sort();
82
+ }
83
+ //# sourceMappingURL=shield.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shield.js","sourceRoot":"","sources":["../src/shield.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=shield.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shield.test.d.ts","sourceRoot":"","sources":["../src/shield.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,39 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { collectActionNamesFromOutputDir, extractActionNamesFromActionsSource, parseShieldManifest, } from './shield.js';
3
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ describe('parseShieldManifest', () => {
7
+ it('accepts v1', () => {
8
+ const m = parseShieldManifest(JSON.stringify({ version: 1, routes: ['/'], actions: ['a'] }));
9
+ expect(m).toEqual({ version: 1, routes: ['/'], actions: ['a'] });
10
+ });
11
+ it('rejects bad version', () => {
12
+ expect(parseShieldManifest(JSON.stringify({ version: 2, routes: [], actions: [] }))).toBeNull();
13
+ });
14
+ });
15
+ describe('extractActionNamesFromActionsSource', () => {
16
+ it('finds registerAction names', () => {
17
+ const src = `
18
+ import { registerAction } from '@nexus_js/server/actions';
19
+ registerAction("foo", fn);
20
+ registerAction("bar-baz", other);
21
+ `;
22
+ expect(extractActionNamesFromActionsSource(src)).toEqual(['foo', 'bar-baz']);
23
+ });
24
+ });
25
+ describe('collectActionNamesFromOutputDir', () => {
26
+ it('walks nested dirs', () => {
27
+ const root = mkdtempSync(join(tmpdir(), 'nexus-shield-'));
28
+ try {
29
+ const nested = join(root, 'blog');
30
+ mkdirSync(nested, { recursive: true });
31
+ writeFileSync(join(nested, 'post.actions.js'), 'registerAction("savePost", x); registerAction("deletePost", y);', 'utf-8');
32
+ expect(collectActionNamesFromOutputDir(root)).toEqual(['deletePost', 'savePost']);
33
+ }
34
+ finally {
35
+ rmSync(root, { recursive: true, force: true });
36
+ }
37
+ });
38
+ });
39
+ //# sourceMappingURL=shield.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shield.test.js","sourceRoot":"","sources":["../src/shield.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,+BAA+B,EAC/B,mCAAmC,EACnC,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE;QACpB,MAAM,CAAC,GAAG,mBAAmB,CAC3B,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAC9D,CAAC;QACF,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAClG,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qCAAqC,EAAE,GAAG,EAAE;IACnD,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,GAAG,GAAG;;;;KAIX,CAAC;QACF,MAAM,CAAC,mCAAmC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC3B,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC;QAC1D,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAClC,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,aAAa,CACX,IAAI,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAC/B,iEAAiE,EACjE,OAAO,CACR,CAAC;YACF,MAAM,CAAC,+BAA+B,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;QACpF,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Nexus Vault-lite — in-memory secret store with hot-reload (no external vault service).
3
+ * Seed once from `process.env`, then merge patches from Studio / `POST /_nexus/dev/vault`.
4
+ */
5
+ export type VaultListener = () => void;
6
+ export declare class NexusVault {
7
+ private readonly store;
8
+ /**
9
+ * One-time copy of all string values from `process.env`.
10
+ * Call when the HTTP server starts so the baseline matches the container env.
11
+ */
12
+ seedFromProcessEnv(): void;
13
+ get(key: string): string | undefined;
14
+ has(key: string): boolean;
15
+ /**
16
+ * Merge keys. Use empty string to remove a key from the vault (not from `process.env` on disk).
17
+ */
18
+ patch(entries: Record<string, string>): void;
19
+ /**
20
+ * Reset to current `process.env`, then apply `entries` (empty string removes a key).
21
+ * Use from Studio “Replace all” to snap back to the process baseline and apply a full `.env`-style paste.
22
+ */
23
+ replaceAll(entries: Record<string, string>): void;
24
+ /** Snapshot for `NexusContext.secrets` (immutable view per request). */
25
+ snapshot(): ReadonlyMap<string, string>;
26
+ subscribe(fn: VaultListener): () => void;
27
+ private notify;
28
+ }
29
+ /** Process-wide vault instance — use this from server code. */
30
+ export declare const nexusVault: NexusVault;
31
+ export declare function getVaultSecretsMap(): ReadonlyMap<string, string>;
32
+ //# sourceMappingURL=vault.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../src/vault.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC;AAIvC,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;IAEnD;;;OAGG;IACH,kBAAkB,IAAI,IAAI;IAM1B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAQ5C;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAUjD,wEAAwE;IACxE,QAAQ,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC;IAIvC,SAAS,CAAC,EAAE,EAAE,aAAa,GAAG,MAAM,IAAI;IAOxC,OAAO,CAAC,MAAM;CASf;AAED,+DAA+D;AAC/D,eAAO,MAAM,UAAU,YAAmB,CAAC;AAE3C,wBAAgB,kBAAkB,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAEhE"}
package/dist/vault.js ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Nexus Vault-lite — in-memory secret store with hot-reload (no external vault service).
3
+ * Seed once from `process.env`, then merge patches from Studio / `POST /_nexus/dev/vault`.
4
+ */
5
+ const vaultListeners = new Set();
6
+ export class NexusVault {
7
+ store = new Map();
8
+ /**
9
+ * One-time copy of all string values from `process.env`.
10
+ * Call when the HTTP server starts so the baseline matches the container env.
11
+ */
12
+ seedFromProcessEnv() {
13
+ for (const [k, v] of Object.entries(process.env)) {
14
+ if (typeof v === 'string')
15
+ this.store.set(k, v);
16
+ }
17
+ }
18
+ get(key) {
19
+ return this.store.get(key);
20
+ }
21
+ has(key) {
22
+ return this.store.has(key);
23
+ }
24
+ /**
25
+ * Merge keys. Use empty string to remove a key from the vault (not from `process.env` on disk).
26
+ */
27
+ patch(entries) {
28
+ for (const [k, v] of Object.entries(entries)) {
29
+ if (v === '')
30
+ this.store.delete(k);
31
+ else
32
+ this.store.set(k, v);
33
+ }
34
+ this.notify();
35
+ }
36
+ /**
37
+ * Reset to current `process.env`, then apply `entries` (empty string removes a key).
38
+ * Use from Studio “Replace all” to snap back to the process baseline and apply a full `.env`-style paste.
39
+ */
40
+ replaceAll(entries) {
41
+ this.store.clear();
42
+ this.seedFromProcessEnv();
43
+ for (const [k, v] of Object.entries(entries)) {
44
+ if (v === '')
45
+ this.store.delete(k);
46
+ else
47
+ this.store.set(k, v);
48
+ }
49
+ this.notify();
50
+ }
51
+ /** Snapshot for `NexusContext.secrets` (immutable view per request). */
52
+ snapshot() {
53
+ return new Map(this.store);
54
+ }
55
+ subscribe(fn) {
56
+ vaultListeners.add(fn);
57
+ return () => {
58
+ vaultListeners.delete(fn);
59
+ };
60
+ }
61
+ notify() {
62
+ for (const fn of vaultListeners) {
63
+ try {
64
+ fn();
65
+ }
66
+ catch {
67
+ /* ignore listener errors */
68
+ }
69
+ }
70
+ }
71
+ }
72
+ /** Process-wide vault instance — use this from server code. */
73
+ export const nexusVault = new NexusVault();
74
+ export function getVaultSecretsMap() {
75
+ return nexusVault.snapshot();
76
+ }
77
+ //# sourceMappingURL=vault.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.js","sourceRoot":"","sources":["../src/vault.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,cAAc,GAAG,IAAI,GAAG,EAAiB,CAAC;AAEhD,MAAM,OAAO,UAAU;IACJ,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEnD;;;OAGG;IACH,kBAAkB;QAChB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACjD,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,GAAG,CAAC,GAAW;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,GAAG,CAAC,GAAW;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAA+B;QACnC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7C,IAAI,CAAC,KAAK,EAAE;gBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;;gBAC9B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED;;;OAGG;IACH,UAAU,CAAC,OAA+B;QACxC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7C,IAAI,CAAC,KAAK,EAAE;gBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;;gBAC9B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,wEAAwE;IACxE,QAAQ;QACN,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,SAAS,CAAC,EAAiB;QACzB,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvB,OAAO,GAAG,EAAE;YACV,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC;IACJ,CAAC;IAEO,MAAM;QACZ,KAAK,MAAM,EAAE,IAAI,cAAc,EAAE,CAAC;YAChC,IAAI,CAAC;gBACH,EAAE,EAAE,CAAC;YACP,CAAC;YAAC,MAAM,CAAC;gBACP,4BAA4B;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAED,+DAA+D;AAC/D,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,UAAU,EAAE,CAAC;AAE3C,MAAM,UAAU,kBAAkB;IAChC,OAAO,UAAU,CAAC,QAAQ,EAAE,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=vault.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.test.d.ts","sourceRoot":"","sources":["../src/vault.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,40 @@
1
+ import { describe, expect, it, beforeEach, afterEach } from 'vitest';
2
+ import { NexusVault } from './vault.js';
3
+ describe('NexusVault', () => {
4
+ const prev = { ...process.env };
5
+ beforeEach(() => {
6
+ process.env['TEST_VAULT_A'] = 'one';
7
+ process.env['TEST_VAULT_B'] = 'two';
8
+ });
9
+ afterEach(() => {
10
+ for (const k of Object.keys(process.env)) {
11
+ if (!(k in prev))
12
+ delete process.env[k];
13
+ }
14
+ for (const [k, v] of Object.entries(prev)) {
15
+ process.env[k] = v;
16
+ }
17
+ });
18
+ it('seeds from env and patches override', () => {
19
+ const v = new NexusVault();
20
+ v.seedFromProcessEnv();
21
+ expect(v.get('TEST_VAULT_A')).toBe('one');
22
+ v.patch({ TEST_VAULT_A: 'hot' });
23
+ expect(v.get('TEST_VAULT_A')).toBe('hot');
24
+ });
25
+ it('patch with empty string removes key', () => {
26
+ const v = new NexusVault();
27
+ v.seedFromProcessEnv();
28
+ v.patch({ TEST_VAULT_B: '' });
29
+ expect(v.has('TEST_VAULT_B')).toBe(false);
30
+ });
31
+ it('replaceAll re-seeds from env then applies entries', () => {
32
+ const v = new NexusVault();
33
+ v.patch({ EPHEMERAL: 'gone' });
34
+ expect(v.get('EPHEMERAL')).toBe('gone');
35
+ v.replaceAll({ EPHEMERAL: '' });
36
+ expect(v.has('EPHEMERAL')).toBe(false);
37
+ expect(v.get('TEST_VAULT_A')).toBe('one');
38
+ });
39
+ });
40
+ //# sourceMappingURL=vault.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.test.js","sourceRoot":"","sources":["../src/vault.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,MAAM,IAAI,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAEhC,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,GAAG,KAAK,CAAC;QACpC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,GAAG,KAAK,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;gBAAE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACrB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,GAAG,IAAI,UAAU,EAAE,CAAC;QAC3B,CAAC,CAAC,kBAAkB,EAAE,CAAC;QACvB,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC;QACjC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,GAAG,IAAI,UAAU,EAAE,CAAC;QAC3B,CAAC,CAAC,kBAAkB,EAAE,CAAC;QACvB,CAAC,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,GAAG,IAAI,UAAU,EAAE,CAAC;QAC3B,CAAC,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,CAAC,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;QAChC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@nexus_js/security",
3
+ "version": "0.7.4",
4
+ "description": "Nexus Hardened Core — Vault-lite (hot secrets) and Shield-lite (build manifest helpers)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "devDependencies": {
15
+ "@types/node": "^22.0.0",
16
+ "typescript": "^5.5.0",
17
+ "vitest": "^2.0.0"
18
+ },
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/bierfor/nexus.git",
23
+ "directory": "packages/security"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.json",
31
+ "dev": "tsc -p tsconfig.json --watch",
32
+ "test": "vitest run",
33
+ "clean": "rm -rf dist"
34
+ }
35
+ }