@sentinel-atl/hardening 0.3.0 → 0.4.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.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Environment variable schema validation — fail fast on misconfiguration.
3
+ *
4
+ * Validates required env vars exist and optional ones have correct formats
5
+ * at startup, before the server begins accepting connections.
6
+ */
7
+ export interface EnvVarDef {
8
+ /** Environment variable name */
9
+ name: string;
10
+ /** Whether this variable is required */
11
+ required?: boolean;
12
+ /** Default value if not set */
13
+ default?: string;
14
+ /** Validation: 'string' | 'number' | 'boolean' | 'url' | 'port' */
15
+ type?: 'string' | 'number' | 'boolean' | 'url' | 'port';
16
+ /** Human-readable description (for error messages) */
17
+ description?: string;
18
+ }
19
+ export interface ValidationResult {
20
+ valid: boolean;
21
+ errors: string[];
22
+ resolved: Record<string, string>;
23
+ }
24
+ /**
25
+ * Validate and resolve environment variables from a schema definition.
26
+ * Returns resolved values (with defaults applied) or throws on error.
27
+ */
28
+ export declare function validateEnv(schema: EnvVarDef[]): ValidationResult;
29
+ /**
30
+ * Validate and throw if any errors — call at server startup.
31
+ */
32
+ export declare function requireValidEnv(schema: EnvVarDef[]): Record<string, string>;
33
+ /**
34
+ * Standard Sentinel env vars for the STP server.
35
+ */
36
+ export declare const SENTINEL_ENV_SCHEMA: EnvVarDef[];
37
+ //# sourceMappingURL=env-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-validation.d.ts","sourceRoot":"","sources":["../src/env-validation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,SAAS;IACxB,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,GAAG,MAAM,CAAC;IACxD,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAID;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,gBAAgB,CAuDjE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAO3E;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,EAU1C,CAAC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Environment variable schema validation — fail fast on misconfiguration.
3
+ *
4
+ * Validates required env vars exist and optional ones have correct formats
5
+ * at startup, before the server begins accepting connections.
6
+ */
7
+ // ─── Validator ───────────────────────────────────────────────────────
8
+ /**
9
+ * Validate and resolve environment variables from a schema definition.
10
+ * Returns resolved values (with defaults applied) or throws on error.
11
+ */
12
+ export function validateEnv(schema) {
13
+ const errors = [];
14
+ const resolved = {};
15
+ for (const def of schema) {
16
+ const raw = process.env[def.name];
17
+ const value = raw ?? def.default;
18
+ if (def.required && !value) {
19
+ errors.push(`Missing required env var: ${def.name}${def.description ? ` (${def.description})` : ''}`);
20
+ continue;
21
+ }
22
+ if (!value)
23
+ continue;
24
+ // Type validation
25
+ switch (def.type) {
26
+ case 'number': {
27
+ const num = Number(value);
28
+ if (isNaN(num)) {
29
+ errors.push(`${def.name}: expected a number, got "${value}"`);
30
+ continue;
31
+ }
32
+ break;
33
+ }
34
+ case 'port': {
35
+ const port = Number(value);
36
+ if (isNaN(port) || port < 1 || port > 65535) {
37
+ errors.push(`${def.name}: expected a port (1-65535), got "${value}"`);
38
+ continue;
39
+ }
40
+ break;
41
+ }
42
+ case 'boolean': {
43
+ if (!['true', 'false', '1', '0'].includes(value.toLowerCase())) {
44
+ errors.push(`${def.name}: expected true/false, got "${value}"`);
45
+ continue;
46
+ }
47
+ break;
48
+ }
49
+ case 'url': {
50
+ try {
51
+ new URL(value);
52
+ }
53
+ catch {
54
+ errors.push(`${def.name}: expected a valid URL, got "${value}"`);
55
+ continue;
56
+ }
57
+ break;
58
+ }
59
+ }
60
+ resolved[def.name] = value;
61
+ }
62
+ return { valid: errors.length === 0, errors, resolved };
63
+ }
64
+ /**
65
+ * Validate and throw if any errors — call at server startup.
66
+ */
67
+ export function requireValidEnv(schema) {
68
+ const result = validateEnv(schema);
69
+ if (!result.valid) {
70
+ const msg = `Configuration errors:\n ${result.errors.join('\n ')}`;
71
+ throw new Error(msg);
72
+ }
73
+ return result.resolved;
74
+ }
75
+ /**
76
+ * Standard Sentinel env vars for the STP server.
77
+ */
78
+ export const SENTINEL_ENV_SCHEMA = [
79
+ { name: 'SENTINEL_PORT', type: 'port', default: '3000', description: 'HTTP port' },
80
+ { name: 'SENTINEL_HOST', type: 'string', default: '0.0.0.0', description: 'Bind address' },
81
+ { name: 'SENTINEL_DATA_DIR', type: 'string', default: './data', description: 'Data directory' },
82
+ { name: 'SENTINEL_API_KEYS', type: 'string', description: 'API keys (key1:scope1,scope2;key2:admin)' },
83
+ { name: 'SENTINEL_CORS_ORIGINS', type: 'string', description: 'Comma-separated allowed origins' },
84
+ { name: 'SENTINEL_TLS_CERT', type: 'string', description: 'TLS certificate path' },
85
+ { name: 'SENTINEL_TLS_KEY', type: 'string', description: 'TLS private key path' },
86
+ { name: 'REDIS_URL', type: 'url', description: 'Redis connection URL' },
87
+ { name: 'NODE_ENV', type: 'string', default: 'production' },
88
+ ];
89
+ //# sourceMappingURL=env-validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-validation.js","sourceRoot":"","sources":["../src/env-validation.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -16,4 +16,5 @@ export { RateLimiter, setRateLimitHeaders, sendRateLimited, parseRateLimit, type
16
16
  export { NonceStore, type NonceStoreConfig, } from './nonce-store.js';
17
17
  export { rotateIfNeeded, cleanupRotatedFiles, totalLogSize, type RotationConfig, } from './audit-rotation.js';
18
18
  export { applySecurityHeaders, securityHeadersConfigFromEnv, type SecurityHeadersConfig, } from './security-headers.js';
19
+ export { validateEnv, requireValidEnv, SENTINEL_ENV_SCHEMA, type EnvVarDef, type ValidationResult, } from './env-validation.js';
19
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,UAAU,GAChB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,UAAU,GAChB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,SAAS,GACf,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,KAAK,aAAa,GACnB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,UAAU,EACV,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,YAAY,EACZ,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,KAAK,qBAAqB,GAC3B,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,UAAU,GAChB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,UAAU,GAChB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,SAAS,GACf,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,KAAK,aAAa,GACnB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,UAAU,EACV,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,YAAY,EACZ,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,KAAK,qBAAqB,GAC3B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,gBAAgB,GACtB,MAAM,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -16,4 +16,5 @@ export { RateLimiter, setRateLimitHeaders, sendRateLimited, parseRateLimit, } fr
16
16
  export { NonceStore, } from './nonce-store.js';
17
17
  export { rotateIfNeeded, cleanupRotatedFiles, totalLogSize, } from './audit-rotation.js';
18
18
  export { applySecurityHeaders, securityHeadersConfigFromEnv, } from './security-headers.js';
19
+ export { validateEnv, requireValidEnv, SENTINEL_ENV_SCHEMA, } from './env-validation.js';
19
20
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAKlB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,iBAAiB,GAElB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,GAEjB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,cAAc,GAEf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,UAAU,GAEX,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,YAAY,GAEb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,oBAAoB,EACpB,4BAA4B,GAE7B,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAKlB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,iBAAiB,GAElB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,GAEjB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,cAAc,GAEf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,UAAU,GAEX,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,YAAY,GAEb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,oBAAoB,EACpB,4BAA4B,GAE7B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,WAAW,EACX,eAAe,EACf,mBAAmB,GAGpB,MAAM,qBAAqB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentinel-atl/hardening",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Production hardening middleware — API key auth, CORS, TLS, rate-limit headers, nonce persistence, audit rotation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -18,7 +18,7 @@
18
18
  "clean": "rm -rf dist"
19
19
  },
20
20
  "dependencies": {
21
- "@sentinel-atl/store": "*"
21
+ "@sentinel-atl/store": "workspace:*"
22
22
  },
23
23
  "devDependencies": {
24
24
  "vitest": "^3.2.4",
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Environment variable schema validation — fail fast on misconfiguration.
3
+ *
4
+ * Validates required env vars exist and optional ones have correct formats
5
+ * at startup, before the server begins accepting connections.
6
+ */
7
+
8
+ // ─── Types ───────────────────────────────────────────────────────────
9
+
10
+ export interface EnvVarDef {
11
+ /** Environment variable name */
12
+ name: string;
13
+ /** Whether this variable is required */
14
+ required?: boolean;
15
+ /** Default value if not set */
16
+ default?: string;
17
+ /** Validation: 'string' | 'number' | 'boolean' | 'url' | 'port' */
18
+ type?: 'string' | 'number' | 'boolean' | 'url' | 'port';
19
+ /** Human-readable description (for error messages) */
20
+ description?: string;
21
+ }
22
+
23
+ export interface ValidationResult {
24
+ valid: boolean;
25
+ errors: string[];
26
+ resolved: Record<string, string>;
27
+ }
28
+
29
+ // ─── Validator ───────────────────────────────────────────────────────
30
+
31
+ /**
32
+ * Validate and resolve environment variables from a schema definition.
33
+ * Returns resolved values (with defaults applied) or throws on error.
34
+ */
35
+ export function validateEnv(schema: EnvVarDef[]): ValidationResult {
36
+ const errors: string[] = [];
37
+ const resolved: Record<string, string> = {};
38
+
39
+ for (const def of schema) {
40
+ const raw = process.env[def.name];
41
+ const value = raw ?? def.default;
42
+
43
+ if (def.required && !value) {
44
+ errors.push(`Missing required env var: ${def.name}${def.description ? ` (${def.description})` : ''}`);
45
+ continue;
46
+ }
47
+
48
+ if (!value) continue;
49
+
50
+ // Type validation
51
+ switch (def.type) {
52
+ case 'number': {
53
+ const num = Number(value);
54
+ if (isNaN(num)) {
55
+ errors.push(`${def.name}: expected a number, got "${value}"`);
56
+ continue;
57
+ }
58
+ break;
59
+ }
60
+ case 'port': {
61
+ const port = Number(value);
62
+ if (isNaN(port) || port < 1 || port > 65535) {
63
+ errors.push(`${def.name}: expected a port (1-65535), got "${value}"`);
64
+ continue;
65
+ }
66
+ break;
67
+ }
68
+ case 'boolean': {
69
+ if (!['true', 'false', '1', '0'].includes(value.toLowerCase())) {
70
+ errors.push(`${def.name}: expected true/false, got "${value}"`);
71
+ continue;
72
+ }
73
+ break;
74
+ }
75
+ case 'url': {
76
+ try {
77
+ new URL(value);
78
+ } catch {
79
+ errors.push(`${def.name}: expected a valid URL, got "${value}"`);
80
+ continue;
81
+ }
82
+ break;
83
+ }
84
+ }
85
+
86
+ resolved[def.name] = value;
87
+ }
88
+
89
+ return { valid: errors.length === 0, errors, resolved };
90
+ }
91
+
92
+ /**
93
+ * Validate and throw if any errors — call at server startup.
94
+ */
95
+ export function requireValidEnv(schema: EnvVarDef[]): Record<string, string> {
96
+ const result = validateEnv(schema);
97
+ if (!result.valid) {
98
+ const msg = `Configuration errors:\n ${result.errors.join('\n ')}`;
99
+ throw new Error(msg);
100
+ }
101
+ return result.resolved;
102
+ }
103
+
104
+ /**
105
+ * Standard Sentinel env vars for the STP server.
106
+ */
107
+ export const SENTINEL_ENV_SCHEMA: EnvVarDef[] = [
108
+ { name: 'SENTINEL_PORT', type: 'port', default: '3000', description: 'HTTP port' },
109
+ { name: 'SENTINEL_HOST', type: 'string', default: '0.0.0.0', description: 'Bind address' },
110
+ { name: 'SENTINEL_DATA_DIR', type: 'string', default: './data', description: 'Data directory' },
111
+ { name: 'SENTINEL_API_KEYS', type: 'string', description: 'API keys (key1:scope1,scope2;key2:admin)' },
112
+ { name: 'SENTINEL_CORS_ORIGINS', type: 'string', description: 'Comma-separated allowed origins' },
113
+ { name: 'SENTINEL_TLS_CERT', type: 'string', description: 'TLS certificate path' },
114
+ { name: 'SENTINEL_TLS_KEY', type: 'string', description: 'TLS private key path' },
115
+ { name: 'REDIS_URL', type: 'url', description: 'Redis connection URL' },
116
+ { name: 'NODE_ENV', type: 'string', default: 'production' },
117
+ ];
package/src/index.ts CHANGED
@@ -60,3 +60,11 @@ export {
60
60
  securityHeadersConfigFromEnv,
61
61
  type SecurityHeadersConfig,
62
62
  } from './security-headers.js';
63
+
64
+ export {
65
+ validateEnv,
66
+ requireValidEnv,
67
+ SENTINEL_ENV_SCHEMA,
68
+ type EnvVarDef,
69
+ type ValidationResult,
70
+ } from './env-validation.js';