@pulse-compute/wasm-guest-link 0.0.0 → 1.0.0-beta.1

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/src/errors.js ADDED
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ const sensitiveKey = /(?:secret|credential|private|token|raw|buffer|payload|contents?|environment)/i;
4
+ const sensitiveEnvironmentName = /(?:secret|token|key|credential|password|passphrase|authorization|cookie)/i;
5
+ const pemBlock = /-----BEGIN [^-]*(?:PRIVATE|SECRET|ENCRYPTED)[^-]*-----[\s\S]*?-----END [^-]*-----/gi;
6
+ const authorizationValue = /\b(?:bearer|basic)\s+[A-Za-z0-9._~+/=-]+/gi;
7
+ const credentialAssignment = /\b(?:secret|token|password|passphrase|credential|private[_-]?key)\s*[=:]\s*[^\s,;]+/gi;
8
+
9
+ function sensitiveValues() {
10
+ return Object.entries(process.env)
11
+ .filter(([name, value]) => sensitiveEnvironmentName.test(name) && typeof value === 'string' && value.length >= 4)
12
+ .map(([, value]) => value)
13
+ .sort((left, right) => right.length - left.length);
14
+ }
15
+
16
+ function safeString(value, secrets = sensitiveValues()) {
17
+ let result = String(value)
18
+ .replace(pemBlock, '<redacted-private-material>')
19
+ .replace(authorizationValue, '<redacted-authorization>')
20
+ .replace(credentialAssignment, '<redacted-credential>');
21
+ for (const secret of secrets) result = result.split(secret).join('<redacted-environment-value>');
22
+ return result.slice(0, 2048);
23
+ }
24
+
25
+ function safeDetails(value, depth = 0, secrets = sensitiveValues()) {
26
+ if (depth > 5) return '<omitted>';
27
+ if (Buffer.isBuffer(value) || ArrayBuffer.isView(value) || value instanceof ArrayBuffer) return '<redacted-bytes>';
28
+ if (Array.isArray(value)) return Object.freeze(value.slice(0, 32).map((entry) => safeDetails(entry, depth + 1, secrets)));
29
+ if (!value || typeof value !== 'object') {
30
+ if (typeof value === 'string') return safeString(value, secrets);
31
+ return value;
32
+ }
33
+ const result = {};
34
+ for (const [key, child] of Object.entries(value)) {
35
+ result[key] = sensitiveKey.test(key) ? '<redacted>' : safeDetails(child, depth + 1, secrets);
36
+ }
37
+ return Object.freeze(result);
38
+ }
39
+
40
+ class GuestLinkError extends Error {
41
+ constructor(code, message, details) {
42
+ super(safeString(message));
43
+ this.name = 'GuestLinkError';
44
+ this.code = code;
45
+ if (details !== undefined) {
46
+ const detail = safeDetails(details);
47
+ this.detail = detail;
48
+ Object.defineProperty(this, 'details', {
49
+ enumerable: false,
50
+ configurable: false,
51
+ writable: false,
52
+ value: detail
53
+ });
54
+ }
55
+ }
56
+ }
57
+
58
+ function fail(code, message, details) {
59
+ throw new GuestLinkError(code, message, details);
60
+ }
61
+
62
+ function wrap(error, code, message, details = {}) {
63
+ if (error instanceof GuestLinkError) throw error;
64
+ fail(code, message, {
65
+ ...details,
66
+ cause: error && error.message ? error.message : String(error)
67
+ });
68
+ }
69
+
70
+ module.exports = Object.freeze({
71
+ GuestLinkError,
72
+ fail,
73
+ wrap,
74
+ safeDetails,
75
+ safeString
76
+ });
package/src/files.js ADDED
@@ -0,0 +1,149 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { diagnosticCodes } = require('./constants.js');
7
+ const { fail } = require('./errors.js');
8
+
9
+ function sha256(value) {
10
+ return crypto.createHash('sha256').update(value).digest('hex');
11
+ }
12
+
13
+ function stableJson(value) {
14
+ return `${JSON.stringify(value, null, 2)}\n`;
15
+ }
16
+
17
+ function slash(value) {
18
+ return String(value).replace(/\\/g, '/');
19
+ }
20
+
21
+ function assertRelativePath(value, field) {
22
+ if (typeof value !== 'string' || value.length === 0) {
23
+ fail(diagnosticCodes.invalid, `${field} must be a non-empty package-relative path.`);
24
+ }
25
+ if (path.posix.isAbsolute(value) || path.win32.isAbsolute(value) || value.includes('\\')) {
26
+ fail(diagnosticCodes.invalid, `${field} must use portable package-relative syntax.`);
27
+ }
28
+ const parts = value.split('/');
29
+ if (parts.some((part) => part === '' || part === '.' || part === '..')) {
30
+ fail(diagnosticCodes.invalid, `${field} must not contain empty, current, or parent path segments.`);
31
+ }
32
+ return value;
33
+ }
34
+
35
+ function inside(root, candidate) {
36
+ const relative = path.relative(root, candidate);
37
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
38
+ }
39
+
40
+ function resolveExistingContained(root, relative, field, expectedKind = 'file') {
41
+ const normalized = assertRelativePath(relative, field);
42
+ const realRoot = fs.realpathSync(root);
43
+ const candidate = path.resolve(realRoot, normalized);
44
+ if (!inside(realRoot, candidate) || !fs.existsSync(candidate)) {
45
+ fail(diagnosticCodes.invalid, `${field} does not resolve to an existing contained ${expectedKind}.`);
46
+ }
47
+ const real = fs.realpathSync(candidate);
48
+ if (!inside(realRoot, real)) {
49
+ fail(diagnosticCodes.invalid, `${field} escapes its package root.`);
50
+ }
51
+ const stat = fs.statSync(real);
52
+ if ((expectedKind === 'file' && !stat.isFile()) || (expectedKind === 'directory' && !stat.isDirectory())) {
53
+ fail(diagnosticCodes.invalid, `${field} must resolve to a ${expectedKind}.`);
54
+ }
55
+ return real;
56
+ }
57
+
58
+ function fileRecord(file) {
59
+ const bytes = fs.readFileSync(file);
60
+ return Object.freeze({ bytes: bytes.length, sha256: sha256(bytes) });
61
+ }
62
+
63
+ function sourceEntries(directory, root = directory, entries = []) {
64
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true }).sort((a, b) => (
65
+ a.name < b.name ? -1 : a.name > b.name ? 1 : 0
66
+ ))) {
67
+ const file = path.join(directory, entry.name);
68
+ const relative = slash(path.relative(root, file));
69
+ const stat = fs.lstatSync(file);
70
+ if (stat.isSymbolicLink()) {
71
+ fail(diagnosticCodes.invalid, `Guest source provenance rejects symbolic link ${relative}.`);
72
+ }
73
+ if (stat.isDirectory()) {
74
+ sourceEntries(file, root, entries);
75
+ continue;
76
+ }
77
+ if (!stat.isFile()) {
78
+ fail(diagnosticCodes.invalid, `Guest source provenance rejects non-file ${relative}.`);
79
+ }
80
+ entries.push(Object.freeze({ relative, file, bytes: stat.size }));
81
+ }
82
+ return entries;
83
+ }
84
+
85
+ function sourceTreeRecord(directory) {
86
+ const entries = sourceEntries(directory);
87
+ if (entries.length === 0) fail(diagnosticCodes.invalid, 'Guest source provenance directory must not be empty.');
88
+ const hash = crypto.createHash('sha256');
89
+ const files = [];
90
+ for (const entry of entries) {
91
+ const bytes = fs.readFileSync(entry.file);
92
+ hash.update(entry.relative, 'utf8');
93
+ hash.update('\0');
94
+ hash.update(String(bytes.length), 'utf8');
95
+ hash.update('\0');
96
+ hash.update(bytes);
97
+ hash.update('\0');
98
+ files.push(Object.freeze({
99
+ file: entry.relative,
100
+ bytes: bytes.length,
101
+ sha256: sha256(bytes)
102
+ }));
103
+ }
104
+ return Object.freeze({
105
+ algorithm: 'path-size-bytes-sha256-v1',
106
+ files: Object.freeze(files),
107
+ bytes: files.reduce((sum, entry) => sum + entry.bytes, 0),
108
+ sha256: hash.digest('hex')
109
+ });
110
+ }
111
+
112
+ function ensureContainedDirectory(projectRoot, relative) {
113
+ const realProject = fs.realpathSync(projectRoot);
114
+ const target = path.resolve(realProject, relative);
115
+ if (!inside(realProject, target)) {
116
+ fail(diagnosticCodes.materializationFailed, 'Guest workspace escapes the project root.');
117
+ }
118
+ fs.mkdirSync(target, { recursive: true });
119
+ const realTarget = fs.realpathSync(target);
120
+ if (!inside(realProject, realTarget)) {
121
+ fail(diagnosticCodes.materializationFailed, 'Guest workspace resolves outside the project root.');
122
+ }
123
+ return realTarget;
124
+ }
125
+
126
+ function writeAtomic(file, bytes) {
127
+ const directory = path.dirname(file);
128
+ fs.mkdirSync(directory, { recursive: true });
129
+ const temporary = path.join(directory, `.${path.basename(file)}.${process.pid}.${crypto.randomBytes(8).toString('hex')}.tmp`);
130
+ try {
131
+ fs.writeFileSync(temporary, bytes);
132
+ fs.renameSync(temporary, file);
133
+ } finally {
134
+ if (fs.existsSync(temporary)) fs.rmSync(temporary, { force: true });
135
+ }
136
+ }
137
+
138
+ module.exports = Object.freeze({
139
+ sha256,
140
+ stableJson,
141
+ slash,
142
+ assertRelativePath,
143
+ inside,
144
+ resolveExistingContained,
145
+ fileRecord,
146
+ sourceTreeRecord,
147
+ ensureContainedDirectory,
148
+ writeAtomic
149
+ });
package/src/index.js ADDED
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const constants = require('./constants.js');
4
+ const contracts = require('./contracts.js');
5
+ const errors = require('./errors.js');
6
+ const files = require('./files.js');
7
+ const inspect = require('./inspect.js');
8
+ const materialize = require('./materialize.js');
9
+ const memoryOwner = require('./memory-owner.js');
10
+ const pipeline = require('./pipeline.js');
11
+ const toolchain = require('./toolchain.js');
12
+
13
+ module.exports = Object.freeze({
14
+ versions: constants.versions,
15
+ memoryAbi: constants.memoryAbi,
16
+ memoryAbiV2: constants.memoryAbiV2,
17
+ es256FrameV2: constants.es256FrameV2,
18
+ es256GuestUnit: constants.es256GuestUnit,
19
+ diagnosticCodes: constants.diagnosticCodes,
20
+ binaryenVersion: constants.binaryenVersion,
21
+ GuestLinkError: errors.GuestLinkError,
22
+ safeDiagnosticDetails: errors.safeDetails,
23
+ safeDiagnosticString: errors.safeString,
24
+ normalizeGuestUnitManifest: contracts.normalizeGuestUnitManifest,
25
+ normalizeGuestUnitPlan: contracts.normalizeGuestUnitPlan,
26
+ sourceTreeRecord: files.sourceTreeRecord,
27
+ inspectWasmFile: inspect.inspectWasmFile,
28
+ materializePackagePrebuilt: materialize.materializePackagePrebuilt,
29
+ writeMemoryOwnerModule: memoryOwner.writeMemoryOwnerModule,
30
+ realizeGuestLinkPlan: pipeline.realizeGuestLinkPlan,
31
+ assertFinalArtifactIdentity: pipeline.assertFinalArtifactIdentity,
32
+ binaryenIdentity: toolchain.binaryenIdentity
33
+ });