asherah 4.0.0-beta.1 → 4.0.0-beta.10

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 CHANGED
@@ -1,19 +1,41 @@
1
- # asherah-node
1
+ # asherah
2
2
 
3
- `asherah-node` packages the Asherah AppEncryption runtime as a Node.js native
4
- addon using `napi-rs`. The crate builds a `cdylib` that is published to npm via
5
- the accompanying workflow.
3
+ Node.js bindings for the Asherah envelope encryption and key rotation library.
4
+
5
+ Prebuilt native binaries are published to npm for Linux (x64/arm64, glibc and
6
+ musl), macOS (x64/arm64), and Windows (x64/arm64). The correct binary is
7
+ selected automatically at install time.
6
8
 
7
9
  ## Features
8
10
 
9
- - Provides synchronous and asynchronous session helpers mirroring the Go SDK.
10
- - Shares configuration parsing through the `asherah-config` crate.
11
- - Leverages the same Rust core (`asherah`) used by other language bindings.
11
+ - Synchronous and asynchronous encrypt/decrypt APIs
12
+ - Compatible with Go, Python, Ruby, Java, and .NET Asherah implementations
13
+ - SQLite, MySQL, PostgreSQL, and DynamoDB metastore support
14
+ - AWS KMS and static key management
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install asherah
20
+ ```
21
+
22
+ ## Quick start
23
+
24
+ ```js
25
+ const asherah = require('asherah');
26
+
27
+ asherah.setup({
28
+ kms: 'static',
29
+ metastore: 'memory',
30
+ serviceName: 'myservice',
31
+ productId: 'myproduct',
32
+ });
12
33
 
13
- ## Building
34
+ const encrypted = asherah.encrypt('partition', Buffer.from('hello world'));
35
+ const decrypted = asherah.decrypt('partition', encrypted);
14
36
 
15
- Use `npm install` in `asherah-node/` to compile the addon locally. CI builds
16
- and publishes prebuilt binaries for supported targets.
37
+ asherah.shutdown();
38
+ ```
17
39
 
18
40
  ## License
19
41
 
package/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export type AsherahConfig = {
7
7
  checkInterval?: number | null;
8
8
  metastore: 'memory' | 'rdbms' | 'dynamodb';
9
9
  connectionString?: string | null;
10
+ replicaReadConsistency?: string | null;
10
11
  dynamoDBEndpoint?: string | null;
11
12
  dynamoDBRegion?: string | null;
12
13
  dynamoDBTableName?: string | null;
@@ -18,10 +19,43 @@ export type AsherahConfig = {
18
19
  enableRegionSuffix?: boolean | null;
19
20
  enableSessionCaching?: boolean | null;
20
21
  verbose?: boolean | null;
22
+ sqlMetastoreDBType?: string | null;
23
+ disableZeroCopy?: boolean | null;
24
+ nullDataCheck?: boolean | null;
25
+ enableCanaries?: boolean | null;
21
26
  };
22
27
 
23
- export declare function setup(config: AsherahConfig): void;
24
- export declare function setupAsync(config: AsherahConfig): Promise<void>;
28
+ /** Canonical godaddy/asherah-node PascalCase config format */
29
+ export type AsherahConfigCompat = {
30
+ readonly ServiceName: string;
31
+ readonly ProductID: string;
32
+ readonly ExpireAfter?: number | null;
33
+ readonly CheckInterval?: number | null;
34
+ readonly Metastore: 'memory' | 'rdbms' | 'dynamodb' | 'test-debug-memory';
35
+ readonly ConnectionString?: string | null;
36
+ readonly DynamoDBEndpoint?: string | null;
37
+ readonly DynamoDBRegion?: string | null;
38
+ readonly DynamoDBTableName?: string | null;
39
+ readonly SessionCacheMaxSize?: number | null;
40
+ readonly SessionCacheDuration?: number | null;
41
+ readonly KMS?: 'aws' | 'static' | 'test-debug-static' | null;
42
+ readonly RegionMap?: Record<string, string> | null;
43
+ readonly PreferredRegion?: string | null;
44
+ readonly EnableRegionSuffix?: boolean | null;
45
+ readonly EnableSessionCaching?: boolean | null;
46
+ readonly Verbose?: boolean | null;
47
+ readonly SQLMetastoreDBType?: string | null;
48
+ readonly ReplicaReadConsistency?: 'eventual' | 'global' | 'session' | null;
49
+ readonly DisableZeroCopy?: boolean | null;
50
+ readonly NullDataCheck?: boolean | null;
51
+ readonly EnableCanaries?: boolean | null;
52
+ };
53
+
54
+ /** Canonical godaddy/asherah-node log hook callback: (level: number, message: string) => void */
55
+ export type LogHookCallback = (level: number, message: string) => void;
56
+
57
+ export declare function setup(config: AsherahConfig | AsherahConfigCompat): void;
58
+ export declare function setupAsync(config: AsherahConfig | AsherahConfigCompat): Promise<void>;
25
59
  export declare function shutdown(): void;
26
60
  export declare function shutdownAsync(): Promise<void>;
27
61
  export declare function getSetupStatus(): boolean;
@@ -39,6 +73,7 @@ export declare function decryptStringAsync(partitionId: string, dataRowRecordJso
39
73
 
40
74
  export declare function setMaxStackAllocItemSize(n: number): void;
41
75
  export declare function setSafetyPaddingOverhead(n: number): void;
76
+
42
77
  export type LogEvent = {
43
78
  level: 'trace' | 'debug' | 'info' | 'warn' | 'error';
44
79
  message: string;
@@ -49,5 +84,19 @@ export type MetricsEvent =
49
84
  | { type: 'encrypt' | 'decrypt' | 'store' | 'load'; durationNs: number }
50
85
  | { type: 'cache_hit' | 'cache_miss'; name: string };
51
86
 
52
- export declare function setLogHook(hook: ((event: LogEvent) => void) | null): void;
87
+ export declare function setLogHook(hook: ((event: LogEvent) => void) | LogHookCallback | null): void;
53
88
  export declare function setMetricsHook(hook: ((event: MetricsEvent) => void) | null): void;
89
+
90
+ // Canonical godaddy/asherah-node snake_case aliases
91
+ export declare function setup_async(config: AsherahConfig | AsherahConfigCompat): Promise<void>;
92
+ export declare function shutdown_async(): Promise<void>;
93
+ export declare function encrypt_async(partitionId: string, data: Buffer): Promise<string>;
94
+ export declare function encrypt_string(partitionId: string, data: string): string;
95
+ export declare function encrypt_string_async(partitionId: string, data: string): Promise<string>;
96
+ export declare function decrypt_async(partitionId: string, dataRowRecordJson: string): Promise<Buffer>;
97
+ export declare function decrypt_string(partitionId: string, dataRowRecordJson: string): string;
98
+ export declare function decrypt_string_async(partitionId: string, dataRowRecordJson: string): Promise<string>;
99
+ export declare function set_max_stack_alloc_item_size(n: number): void;
100
+ export declare function set_safety_padding_overhead(n: number): void;
101
+ export declare function set_log_hook(hook: ((event: LogEvent) => void) | LogHookCallback | null): void;
102
+ export declare function get_setup_status(): boolean;
package/npm/index.js CHANGED
@@ -1,6 +1,24 @@
1
1
  const path = require('path');
2
2
  const os = require('os');
3
3
 
4
+ // Detect musl libc (Alpine Linux, etc.)
5
+ function isMusl() {
6
+ if (os.platform() !== 'linux') return false;
7
+ try {
8
+ // Node 18+: process.report.getReport() exposes glibc version
9
+ const report = process.report.getReport();
10
+ const header = typeof report === 'string' ? JSON.parse(report).header : report.header;
11
+ return !header.glibcVersionRuntime;
12
+ } catch {
13
+ // Fallback: check for musl dynamic linker
14
+ try {
15
+ return require('fs').readdirSync('/lib').some(f => f.startsWith('ld-musl-'));
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+ }
21
+
4
22
  // Determine current platform
5
23
  function getPlatform() {
6
24
  const type = os.platform();
@@ -11,11 +29,13 @@ function getPlatform() {
11
29
  if (arch === 'arm64') return 'darwin-arm64';
12
30
  }
13
31
  if (type === 'linux') {
14
- if (arch === 'x64') return 'linux-x64-gnu';
15
- if (arch === 'arm64') return 'linux-arm64-gnu';
32
+ const libc = isMusl() ? 'musl' : 'gnu';
33
+ if (arch === 'x64') return `linux-x64-${libc}`;
34
+ if (arch === 'arm64') return `linux-arm64-${libc}`;
16
35
  }
17
36
  if (type === 'win32') {
18
37
  if (arch === 'x64') return 'win32-x64-msvc';
38
+ if (arch === 'arm64') return 'win32-arm64-msvc';
19
39
  }
20
40
 
21
41
  throw new Error(`Unsupported platform: ${type}-${arch}`);
@@ -23,21 +43,41 @@ function getPlatform() {
23
43
 
24
44
  const platform = getPlatform();
25
45
 
26
- // Try to load the native module
46
+ // Map platform to npm package name (win32 needed a different name to avoid npm spam filter)
47
+ const PLATFORM_PACKAGES = {
48
+ 'darwin-arm64': 'asherah-darwin-arm64',
49
+ 'darwin-x64': 'asherah-darwin-x64',
50
+ 'linux-x64-gnu': 'asherah-linux-x64-gnu',
51
+ 'linux-arm64-gnu': 'asherah-linux-arm64-gnu',
52
+ 'linux-x64-musl': 'asherah-linux-x64-musl',
53
+ 'linux-arm64-musl': 'asherah-linux-arm64-musl',
54
+ 'win32-x64-msvc': 'asherah-windows-x64',
55
+ 'win32-arm64-msvc': 'asherah-windows-arm64',
56
+ };
57
+ const packageName = PLATFORM_PACKAGES[platform] || `asherah-${platform}`;
58
+
59
+ // Try to load the native module:
60
+ // 1. Installed platform package (optionalDependency) — normal npm install
61
+ // 2. Bundled in platform subdirectory — development / CI builds
62
+ // 3. Legacy single-binary fallback
63
+ let native = null;
64
+ let lastErr = null;
65
+
27
66
  const attempts = [
28
- // Platform-specific directory (for universal package)
67
+ // Platform package installed as optionalDependency (e.g., asherah-darwin-arm64)
68
+ packageName,
69
+ // Bundled in platform subdirectory (development builds)
29
70
  path.join(__dirname, platform, `index.${platform}.node`),
30
- // Fallback to old single-binary location
71
+ // Fallback to old single-binary locations
31
72
  path.join(__dirname, 'asherah.node'),
32
73
  path.join(__dirname, '..', 'index.node'),
33
74
  ];
34
75
 
35
- let lastErr = null;
36
76
  for (const candidate of attempts) {
37
77
  try {
38
- module.exports = require(candidate);
39
- module.exports.__binary = candidate;
40
- return;
78
+ native = require(candidate);
79
+ native.__binary = candidate;
80
+ break;
41
81
  } catch (err) {
42
82
  lastErr = err;
43
83
  if (
@@ -50,5 +90,141 @@ for (const candidate of attempts) {
50
90
  }
51
91
  }
52
92
 
53
- const detail = lastErr ? `: ${lastErr.message || String(lastErr)}` : '';
54
- throw new Error(`Failed to load Asherah native addon for ${platform}${detail}`);
93
+ if (!native) {
94
+ const detail = lastErr ? `: ${lastErr.message || String(lastErr)}` : '';
95
+ throw new Error(
96
+ `Failed to load Asherah native addon for ${platform}. ` +
97
+ `Ensure the platform package '${packageName}' is installed${detail}`
98
+ );
99
+ }
100
+
101
+ // --- Canonical godaddy/asherah-node compatibility layer ---
102
+
103
+ // PascalCase → camelCase config field mapping
104
+ const CONFIG_MAP = {
105
+ ServiceName: 'serviceName',
106
+ ProductID: 'productId',
107
+ ExpireAfter: 'expireAfter',
108
+ CheckInterval: 'checkInterval',
109
+ Metastore: 'metastore',
110
+ ConnectionString: 'connectionString',
111
+ DynamoDBEndpoint: 'dynamoDBEndpoint',
112
+ DynamoDBRegion: 'dynamoDBRegion',
113
+ DynamoDBTableName: 'dynamoDBTableName',
114
+ SessionCacheMaxSize: 'sessionCacheMaxSize',
115
+ SessionCacheDuration: 'sessionCacheDuration',
116
+ KMS: 'kms',
117
+ RegionMap: 'regionMap',
118
+ PreferredRegion: 'preferredRegion',
119
+ EnableRegionSuffix: 'enableRegionSuffix',
120
+ EnableSessionCaching: 'enableSessionCaching',
121
+ Verbose: 'verbose',
122
+ SQLMetastoreDBType: 'sqlMetastoreDBType',
123
+ ReplicaReadConsistency: 'replicaReadConsistency',
124
+ DisableZeroCopy: 'disableZeroCopy',
125
+ NullDataCheck: 'nullDataCheck',
126
+ EnableCanaries: 'enableCanaries',
127
+ };
128
+
129
+ // Legacy/debug metastore aliases (match Go behavior)
130
+ const METASTORE_ALIASES = {
131
+ 'test-debug-memory': 'memory',
132
+ 'test-debug-sqlite': 'sqlite',
133
+ 'test-debug-static': 'static',
134
+ };
135
+
136
+ // Legacy/debug KMS aliases
137
+ const KMS_ALIASES = {
138
+ 'test-debug-static': 'static',
139
+ };
140
+
141
+ function normalizeConfig(config) {
142
+ // Detect PascalCase format by checking for ServiceName (capital S)
143
+ if (!config || typeof config !== 'object' || !('ServiceName' in config)) {
144
+ return config;
145
+ }
146
+
147
+ const out = {};
148
+ for (const [key, value] of Object.entries(config)) {
149
+ const mapped = CONFIG_MAP[key];
150
+ if (mapped === undefined) {
151
+ // Unknown field — pass through as-is (may be a camelCase field mixed in)
152
+ out[key] = value;
153
+ } else if (mapped !== null) {
154
+ out[mapped] = value;
155
+ }
156
+ // mapped === null means ignored (Go-specific)
157
+ }
158
+
159
+ // Normalize metastore aliases
160
+ if (typeof out.metastore === 'string') {
161
+ const lower = out.metastore.toLowerCase();
162
+ out.metastore = METASTORE_ALIASES[lower] || lower;
163
+ }
164
+
165
+ // Normalize KMS aliases
166
+ if (typeof out.kms === 'string') {
167
+ const lower = out.kms.toLowerCase();
168
+ out.kms = KMS_ALIASES[lower] || lower;
169
+ }
170
+
171
+ return out;
172
+ }
173
+
174
+ // Log level mapping: Rust level string → zerolog numeric (used by Go asherah)
175
+ const LEVEL_TO_NUMBER = {
176
+ trace: -1,
177
+ debug: 0,
178
+ info: 1,
179
+ warn: 2,
180
+ error: 3,
181
+ };
182
+
183
+ // Wrap setup to normalize config
184
+ function setup(config) {
185
+ return native.setup(normalizeConfig(config));
186
+ }
187
+
188
+ function setupAsync(config) {
189
+ return native.setupAsync(normalizeConfig(config));
190
+ }
191
+
192
+ // set_log_hook: accept canonical (level, message) callback or native (event) callback
193
+ function set_log_hook(callback) {
194
+ if (callback == null) {
195
+ return native.setLogHook(null);
196
+ }
197
+ // Canonical callback: (level: number, message: string) => void (arity 2)
198
+ // Native callback: (event: {level, message, target}) => void (arity 1)
199
+ if (callback.length >= 2) {
200
+ return native.setLogHook(function (event) {
201
+ const numLevel =
202
+ LEVEL_TO_NUMBER[event.level] !== undefined
203
+ ? LEVEL_TO_NUMBER[event.level]
204
+ : 1;
205
+ callback(numLevel, event.message);
206
+ });
207
+ }
208
+ return native.setLogHook(callback);
209
+ }
210
+
211
+ // Export everything from native addon
212
+ Object.assign(module.exports, native);
213
+
214
+ // Override setup/setupAsync with normalizing versions
215
+ module.exports.setup = setup;
216
+ module.exports.setupAsync = setupAsync;
217
+
218
+ // snake_case aliases for canonical API compatibility
219
+ module.exports.setup_async = setupAsync;
220
+ module.exports.shutdown_async = native.shutdownAsync;
221
+ module.exports.encrypt_async = native.encryptAsync;
222
+ module.exports.encrypt_string = native.encryptString;
223
+ module.exports.encrypt_string_async = native.encryptStringAsync;
224
+ module.exports.decrypt_async = native.decryptAsync;
225
+ module.exports.decrypt_string = native.decryptString;
226
+ module.exports.decrypt_string_async = native.decryptStringAsync;
227
+ module.exports.set_max_stack_alloc_item_size = native.setMaxStackAllocItemSize;
228
+ module.exports.set_safety_padding_overhead = native.setSafetyPaddingOverhead;
229
+ module.exports.set_log_hook = set_log_hook;
230
+ module.exports.get_setup_status = native.getSetupStatus;
package/package.json CHANGED
@@ -1,21 +1,34 @@
1
1
  {
2
2
  "name": "asherah",
3
- "version": "4.0.0-beta.1",
3
+ "version": "4.0.0-beta.10",
4
4
  "private": false,
5
5
  "description": "Asherah envelope encryption and key rotation library",
6
6
  "main": "npm/index.js",
7
7
  "types": "index.d.ts",
8
8
  "files": [
9
- "npm/**/*",
9
+ "npm/index.js",
10
10
  "index.d.ts",
11
11
  "README.md",
12
12
  "LICENSE"
13
13
  ],
14
+ "napi": {
15
+ "binaryName": "index",
16
+ "targets": [
17
+ "x86_64-apple-darwin",
18
+ "aarch64-apple-darwin",
19
+ "x86_64-unknown-linux-gnu",
20
+ "aarch64-unknown-linux-gnu",
21
+ "x86_64-unknown-linux-musl",
22
+ "aarch64-unknown-linux-musl",
23
+ "x86_64-pc-windows-msvc",
24
+ "aarch64-pc-windows-msvc"
25
+ ]
26
+ },
14
27
  "scripts": {
15
28
  "build": "napi build",
16
29
  "build:release": "napi build --release",
17
- "prepublishOnly": "napi prepublish -t npm",
18
- "test": "node test/roundtrip.js"
30
+ "test": "node test/roundtrip.js",
31
+ "test:bun": "bun test/roundtrip.js"
19
32
  },
20
33
  "devDependencies": {
21
34
  "@napi-rs/cli": "^2.18.0"
@@ -24,9 +37,13 @@
24
37
  "node": ">= 18"
25
38
  },
26
39
  "optionalDependencies": {
27
- "asherah-win32-x64-msvc": "4.0.0",
28
- "asherah-darwin-x64": "4.0.0",
29
- "asherah-linux-x64-gnu": "4.0.0",
30
- "asherah-linux-arm64-gnu": "4.0.0"
40
+ "asherah-darwin-arm64": "4.0.0-beta.10",
41
+ "asherah-darwin-x64": "4.0.0-beta.10",
42
+ "asherah-linux-x64-gnu": "4.0.0-beta.10",
43
+ "asherah-linux-arm64-gnu": "4.0.0-beta.10",
44
+ "asherah-linux-x64-musl": "4.0.0-beta.10",
45
+ "asherah-linux-arm64-musl": "4.0.0-beta.10",
46
+ "asherah-windows-x64": "4.0.0-beta.10",
47
+ "asherah-windows-arm64": "4.0.0-beta.10"
31
48
  }
32
49
  }