@redact-secret/core 0.1.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/LICENSE +21 -0
- package/README.md +202 -0
- package/dist/adapters/node-stream.d.ts +51 -0
- package/dist/adapters/node-stream.js +79 -0
- package/dist/adapters/shared.d.ts +33 -0
- package/dist/adapters/shared.js +81 -0
- package/dist/adapters/web-stream.d.ts +57 -0
- package/dist/adapters/web-stream.js +115 -0
- package/dist/errors.d.ts +41 -0
- package/dist/errors.js +82 -0
- package/dist/formatters.d.ts +23 -0
- package/dist/formatters.js +22 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +62 -0
- package/dist/native.d.ts +80 -0
- package/dist/native.js +21 -0
- package/dist/runtime/browser.d.ts +83 -0
- package/dist/runtime/browser.js +143 -0
- package/dist/runtime/node.d.ts +58 -0
- package/dist/runtime/node.js +122 -0
- package/dist/runtime.d.ts +29 -0
- package/dist/runtime.js +256 -0
- package/dist/session.d.ts +13 -0
- package/dist/session.js +15 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.js +14 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +8 -0
- package/package.json +66 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Node.js adapter: the N-API native addon, normalized to the internal
|
|
3
|
+
* binding contract (`decision-define-runtime-bindings`).
|
|
4
|
+
*
|
|
5
|
+
* The package's `imports` map reaches this module only under the `node`
|
|
6
|
+
* condition, so a browser build never resolves it and never pulls in the
|
|
7
|
+
* `node:` module below.
|
|
8
|
+
*
|
|
9
|
+
* Node's own loading has nothing to await, so `initialize()` here is a fast
|
|
10
|
+
* idempotent no-op in the addon. It stays part of the contract regardless, so
|
|
11
|
+
* the usage model does not vary by runtime.
|
|
12
|
+
*/
|
|
13
|
+
import { createRequire } from "node:module";
|
|
14
|
+
import { SecretScanError } from "../errors.js";
|
|
15
|
+
/**
|
|
16
|
+
* One `optionalDependencies` entry per `node-publish-targets`, published in
|
|
17
|
+
* lockstep with this package (`bindings/node/npm/<platform>/package.json`).
|
|
18
|
+
* `os`/`cpu`/`libc` on each of those manifests is what makes every
|
|
19
|
+
* non-matching entry optional in the literal npm sense: an install skips
|
|
20
|
+
* the ones that do not match instead of failing on them.
|
|
21
|
+
*
|
|
22
|
+
* `node-publish-targets` is six of the eight triples `napi.targets` builds
|
|
23
|
+
* and qualifies: npm ships glibc only
|
|
24
|
+
* (`decision-ship-first-release-artifact-set`), so the two musl triples have
|
|
25
|
+
* no entry here and no libc dimension below — there is nothing for one to
|
|
26
|
+
* select between. A musl host's `process.platform`/`process.arch` still
|
|
27
|
+
* matches the `linux` glibc entry, and each manifest's `libc` field is not
|
|
28
|
+
* reliably enforced by every npm version, so a musl install can still
|
|
29
|
+
* resolve and install the glibc package (`docs/qualification.md`).
|
|
30
|
+
* `loadAddon`'s `require` of a glibc-linked `.node` file then fails to load
|
|
31
|
+
* under a musl runtime, caught the same way a missing optional dependency
|
|
32
|
+
* on any platform is, reaching the same `INITIALIZATION_FAILED` an
|
|
33
|
+
* explicitly unsupported host gets. `scripts/check-artifact-matrix.py`
|
|
34
|
+
* requires this mapping, the six `bindings/node/npm/<platform>/package.json`
|
|
35
|
+
* manifests, and this package's own `optionalDependencies` to name exactly
|
|
36
|
+
* the same six packages.
|
|
37
|
+
*/
|
|
38
|
+
const PLATFORM_PACKAGES = {
|
|
39
|
+
darwin: {
|
|
40
|
+
arm64: "@redact-secret/node-darwin-arm64",
|
|
41
|
+
x64: "@redact-secret/node-darwin-x64",
|
|
42
|
+
},
|
|
43
|
+
linux: {
|
|
44
|
+
arm64: "@redact-secret/node-linux-arm64-gnu",
|
|
45
|
+
x64: "@redact-secret/node-linux-x64-gnu",
|
|
46
|
+
},
|
|
47
|
+
win32: {
|
|
48
|
+
arm64: "@redact-secret/node-win32-arm64-msvc",
|
|
49
|
+
x64: "@redact-secret/node-win32-x64-msvc",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The addon package this host should have installed, or `undefined` on a
|
|
54
|
+
* platform/architecture this package ships no addon for at all — the
|
|
55
|
+
* runtime fallback that keeps an unsupported host's failure identical to a
|
|
56
|
+
* supported host whose optional dependency did not install: both reach
|
|
57
|
+
* `loadAddon`'s own `INITIALIZATION_FAILED`, never a raw `require` error.
|
|
58
|
+
*
|
|
59
|
+
* Exported so `scripts/qualify-node-addon.mjs` and
|
|
60
|
+
* `scripts/qualify-package-consumer.mjs` compute the same host-to-package
|
|
61
|
+
* mapping this module actually loads from, instead of restating it.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveAddonSpecifier() {
|
|
64
|
+
return PLATFORM_PACKAGES[process.platform]?.[process.arch];
|
|
65
|
+
}
|
|
66
|
+
function loadAddon() {
|
|
67
|
+
const specifier = resolveAddonSpecifier();
|
|
68
|
+
if (specifier === undefined) {
|
|
69
|
+
throw new SecretScanError("INITIALIZATION_FAILED");
|
|
70
|
+
}
|
|
71
|
+
const require = createRequire(import.meta.url);
|
|
72
|
+
let addon;
|
|
73
|
+
try {
|
|
74
|
+
addon = require(specifier);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Not installed (an optional dependency npm skipped, or one that failed
|
|
78
|
+
// to install) and a corrupt addon both fail the same fixed way.
|
|
79
|
+
throw new SecretScanError("INITIALIZATION_FAILED");
|
|
80
|
+
}
|
|
81
|
+
for (const name of [
|
|
82
|
+
"version",
|
|
83
|
+
"initialize",
|
|
84
|
+
"scan",
|
|
85
|
+
"redact",
|
|
86
|
+
"scanAndRedact",
|
|
87
|
+
]) {
|
|
88
|
+
if (typeof addon[name] !== "function") {
|
|
89
|
+
throw new SecretScanError("INITIALIZATION_FAILED");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return addon;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Builds the internal binding contract from an already-loaded addon.
|
|
96
|
+
*
|
|
97
|
+
* Exported so a test double can exercise this exact normalization —
|
|
98
|
+
* including the `createIncrementalSanitizer` fallback — without loading the
|
|
99
|
+
* real addon, the way `runtime/browser.ts`'s
|
|
100
|
+
* `createBindingFromWasmModule` does for the WebAssembly artifact.
|
|
101
|
+
*/
|
|
102
|
+
export function createBindingFromAddon(addon) {
|
|
103
|
+
return {
|
|
104
|
+
version: () => addon.version(),
|
|
105
|
+
initialize: () => {
|
|
106
|
+
addon.initialize();
|
|
107
|
+
},
|
|
108
|
+
scan: (input, policy) => addon.scan(input, policy),
|
|
109
|
+
redact: (input, findings, formatter) => addon.redact(input, findings, formatter),
|
|
110
|
+
scanAndRedact: (input, policy, formatter) => {
|
|
111
|
+
const result = addon.scanAndRedact(input, policy, formatter);
|
|
112
|
+
return { text: result.redacted, findings: result.findings };
|
|
113
|
+
},
|
|
114
|
+
createIncrementalSanitizer: (options) => {
|
|
115
|
+
if (typeof addon.createIncrementalSanitizer !== "function") {
|
|
116
|
+
throw new SecretScanError("INCREMENTAL_UNAVAILABLE");
|
|
117
|
+
}
|
|
118
|
+
return addon.createIncrementalSanitizer(options);
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export const loadNativeBinding = async () => createBindingFromAddon(loadAddon());
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime-neutral half of the package: the initialization contract and
|
|
3
|
+
* the operations it gates.
|
|
4
|
+
*
|
|
5
|
+
* {@link createRedactSecretRuntime} takes the loader for one runtime and returns
|
|
6
|
+
* the public operations bound to it. `index.ts` supplies the loader the
|
|
7
|
+
* package's `imports` map selected; tests supply their own. Nothing here
|
|
8
|
+
* inspects the host, imports a `node:` module, or touches a global.
|
|
9
|
+
*/
|
|
10
|
+
import { type NativeBindingLoader } from "./native.js";
|
|
11
|
+
import type { IncrementalSanitizer, IncrementalSanitizerOptions, RedactOptions, ScanAndRedactOptions, ScanOptions, ScanResult, SecretFinding } from "./types.js";
|
|
12
|
+
export interface RedactSecretRuntime {
|
|
13
|
+
initialize(): Promise<void>;
|
|
14
|
+
scan(input: string, options?: ScanOptions): readonly SecretFinding[];
|
|
15
|
+
redact(input: string, findings: readonly SecretFinding[], options?: RedactOptions): string;
|
|
16
|
+
scanAndRedact(input: string, options?: ScanAndRedactOptions): ScanResult;
|
|
17
|
+
createIncrementalSanitizer(options: IncrementalSanitizerOptions): IncrementalSanitizer;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Binds the public operations to one runtime's loader.
|
|
21
|
+
*
|
|
22
|
+
* `initialize` is the whole lifecycle contract: it may be awaited any number
|
|
23
|
+
* of times from any number of call sites and loads at most once, it verifies
|
|
24
|
+
* that the loaded artifact reports this package's version
|
|
25
|
+
* (`decision-release-bindings-in-lockstep`), and every synchronous operation
|
|
26
|
+
* below fails with `NOT_INITIALIZED` until exactly one call has succeeded. A
|
|
27
|
+
* failed attempt is not cached: a caller may retry.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createRedactSecretRuntime(loadNativeBinding: NativeBindingLoader): RedactSecretRuntime;
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime-neutral half of the package: the initialization contract and
|
|
3
|
+
* the operations it gates.
|
|
4
|
+
*
|
|
5
|
+
* {@link createRedactSecretRuntime} takes the loader for one runtime and returns
|
|
6
|
+
* the public operations bound to it. `index.ts` supplies the loader the
|
|
7
|
+
* package's `imports` map selected; tests supply their own. Nothing here
|
|
8
|
+
* inspects the host, imports a `node:` module, or touches a global.
|
|
9
|
+
*/
|
|
10
|
+
import { SecretScanError, toSecretScanError, } from "./errors.js";
|
|
11
|
+
import { defaultPlaceholderFormatter } from "./formatters.js";
|
|
12
|
+
import { NATIVE_HANDLE, } from "./native.js";
|
|
13
|
+
import { VERSION } from "./version.js";
|
|
14
|
+
/** Freezes the five documented fields a policy callback is allowed to see. */
|
|
15
|
+
function toDetectedSecretFinding(finding) {
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
id: finding.id,
|
|
18
|
+
type: finding.type,
|
|
19
|
+
detector: finding.detector,
|
|
20
|
+
confidence: finding.confidence,
|
|
21
|
+
start: finding.start,
|
|
22
|
+
end: finding.end,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/** Freezes the seven documented fields, preserving any binding handle. */
|
|
26
|
+
function toSecretFinding(finding) {
|
|
27
|
+
const published = {
|
|
28
|
+
id: finding.id,
|
|
29
|
+
type: finding.type,
|
|
30
|
+
detector: finding.detector,
|
|
31
|
+
confidence: finding.confidence,
|
|
32
|
+
action: finding.action,
|
|
33
|
+
start: finding.start,
|
|
34
|
+
end: finding.end,
|
|
35
|
+
};
|
|
36
|
+
const handle = finding[NATIVE_HANDLE];
|
|
37
|
+
if (handle !== undefined) {
|
|
38
|
+
Object.defineProperty(published, NATIVE_HANDLE, {
|
|
39
|
+
value: handle,
|
|
40
|
+
enumerable: false,
|
|
41
|
+
writable: false,
|
|
42
|
+
configurable: false,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return Object.freeze(published);
|
|
46
|
+
}
|
|
47
|
+
function toSecretFindings(findings) {
|
|
48
|
+
return Object.freeze(findings.map(toSecretFinding));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Re-presents a public finding to the binding that produced it, carrying the
|
|
52
|
+
* binding handle back across when the finding has one.
|
|
53
|
+
*
|
|
54
|
+
* The WebAssembly binding's `redact` accepts only the opaque findings its own
|
|
55
|
+
* `scan` returned; that adapter rejects a finding that arrives without one
|
|
56
|
+
* rather than reinterpreting it. The Node addon reads the plain fields.
|
|
57
|
+
*/
|
|
58
|
+
function toNativeFinding(finding) {
|
|
59
|
+
const handle = finding[NATIVE_HANDLE];
|
|
60
|
+
if (handle === undefined)
|
|
61
|
+
return finding;
|
|
62
|
+
return { ...finding, [NATIVE_HANDLE]: handle };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Matches a JavaScript string containing a lone (unpaired) UTF-16 surrogate:
|
|
66
|
+
* a high surrogate not immediately followed by a low surrogate, or a low
|
|
67
|
+
* surrogate not immediately preceded by a high surrogate. Such a code unit
|
|
68
|
+
* has no UTF-8 representation, so it cannot cross into either binding's Rust
|
|
69
|
+
* `&str` (`errors.ts`'s `UNPAIRED_SURROGATE` documentation).
|
|
70
|
+
*/
|
|
71
|
+
const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
72
|
+
function requireString(value) {
|
|
73
|
+
if (typeof value !== "string")
|
|
74
|
+
throw new SecretScanError("INVALID_INPUT");
|
|
75
|
+
if (LONE_SURROGATE.test(value)) {
|
|
76
|
+
throw new SecretScanError("UNPAIRED_SURROGATE");
|
|
77
|
+
}
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
function toPolicyCallback(policy) {
|
|
81
|
+
if (policy === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
if (typeof policy !== "object" || typeof policy.evaluate !== "function") {
|
|
84
|
+
throw new SecretScanError("INVALID_OPTIONS");
|
|
85
|
+
}
|
|
86
|
+
return (finding, context) => policy.evaluate(toDetectedSecretFinding(finding), context);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Routes the exported default formatter back to the binding's own built-in
|
|
90
|
+
* rather than calling across the boundary for every placeholder, so the
|
|
91
|
+
* default path stays exactly the core's.
|
|
92
|
+
*/
|
|
93
|
+
function toFormatterCallback(formatter) {
|
|
94
|
+
if (formatter === undefined || formatter === defaultPlaceholderFormatter) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
if (typeof formatter !== "function") {
|
|
98
|
+
throw new SecretScanError("INVALID_OPTIONS");
|
|
99
|
+
}
|
|
100
|
+
return (finding, context) => formatter(toSecretFinding(finding), context);
|
|
101
|
+
}
|
|
102
|
+
function toNativeIncrementalOptions(options) {
|
|
103
|
+
if (typeof options !== "object" || options === null) {
|
|
104
|
+
throw new SecretScanError("INVALID_OPTIONS");
|
|
105
|
+
}
|
|
106
|
+
const { limits, policy, placeholderFormatter } = options;
|
|
107
|
+
if (typeof limits !== "object" || limits === null) {
|
|
108
|
+
throw new SecretScanError("INVALID_LIMITS");
|
|
109
|
+
}
|
|
110
|
+
const formatter = toFormatterCallback(placeholderFormatter);
|
|
111
|
+
if (policy !== undefined &&
|
|
112
|
+
(typeof policy !== "object" || typeof policy.evaluate !== "function")) {
|
|
113
|
+
throw new SecretScanError("INVALID_OPTIONS");
|
|
114
|
+
}
|
|
115
|
+
const policyCallback = policy === undefined
|
|
116
|
+
? undefined
|
|
117
|
+
: (finding, context) => policy.evaluate(toDetectedSecretFinding(finding), context);
|
|
118
|
+
return {
|
|
119
|
+
limits: {
|
|
120
|
+
maxInputCodeUnits: limits.maxInputCodeUnits,
|
|
121
|
+
maxBufferedCodeUnits: limits.maxBufferedCodeUnits,
|
|
122
|
+
maxTokenCodeUnits: limits.maxTokenCodeUnits,
|
|
123
|
+
maxMultilineCodeUnits: limits.maxMultilineCodeUnits,
|
|
124
|
+
},
|
|
125
|
+
...(policyCallback === undefined ? {} : { policy: policyCallback }),
|
|
126
|
+
...(formatter === undefined ? {} : { formatter }),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Binds the public operations to one runtime's loader.
|
|
131
|
+
*
|
|
132
|
+
* `initialize` is the whole lifecycle contract: it may be awaited any number
|
|
133
|
+
* of times from any number of call sites and loads at most once, it verifies
|
|
134
|
+
* that the loaded artifact reports this package's version
|
|
135
|
+
* (`decision-release-bindings-in-lockstep`), and every synchronous operation
|
|
136
|
+
* below fails with `NOT_INITIALIZED` until exactly one call has succeeded. A
|
|
137
|
+
* failed attempt is not cached: a caller may retry.
|
|
138
|
+
*/
|
|
139
|
+
export function createRedactSecretRuntime(loadNativeBinding) {
|
|
140
|
+
let binding;
|
|
141
|
+
let pending;
|
|
142
|
+
async function load() {
|
|
143
|
+
let loaded;
|
|
144
|
+
try {
|
|
145
|
+
loaded = await loadNativeBinding();
|
|
146
|
+
if (loaded.version() !== VERSION) {
|
|
147
|
+
throw new SecretScanError("INITIALIZATION_FAILED");
|
|
148
|
+
}
|
|
149
|
+
loaded.initialize();
|
|
150
|
+
}
|
|
151
|
+
catch (thrown) {
|
|
152
|
+
throw toSecretScanError(thrown, "INITIALIZATION_FAILED");
|
|
153
|
+
}
|
|
154
|
+
binding = loaded;
|
|
155
|
+
}
|
|
156
|
+
function initialize() {
|
|
157
|
+
if (binding !== undefined)
|
|
158
|
+
return Promise.resolve();
|
|
159
|
+
pending ??= load().finally(() => {
|
|
160
|
+
pending = undefined;
|
|
161
|
+
});
|
|
162
|
+
return pending;
|
|
163
|
+
}
|
|
164
|
+
function active() {
|
|
165
|
+
if (binding === undefined)
|
|
166
|
+
throw new SecretScanError("NOT_INITIALIZED");
|
|
167
|
+
return binding;
|
|
168
|
+
}
|
|
169
|
+
function scan(input, options) {
|
|
170
|
+
const native = active();
|
|
171
|
+
const text = requireString(input);
|
|
172
|
+
const policy = toPolicyCallback(options?.policy);
|
|
173
|
+
try {
|
|
174
|
+
return toSecretFindings(native.scan(text, policy));
|
|
175
|
+
}
|
|
176
|
+
catch (thrown) {
|
|
177
|
+
throw toSecretScanError(thrown, "DETECTOR_FAILURE");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function redact(input, findings, options) {
|
|
181
|
+
const native = active();
|
|
182
|
+
const text = requireString(input);
|
|
183
|
+
if (!Array.isArray(findings)) {
|
|
184
|
+
throw new SecretScanError("INVALID_FINDINGS");
|
|
185
|
+
}
|
|
186
|
+
const formatter = toFormatterCallback(options?.placeholderFormatter);
|
|
187
|
+
try {
|
|
188
|
+
return native.redact(text, findings.map(toNativeFinding), formatter);
|
|
189
|
+
}
|
|
190
|
+
catch (thrown) {
|
|
191
|
+
throw toSecretScanError(thrown, "INVALID_FINDINGS");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function scanAndRedact(input, options) {
|
|
195
|
+
const native = active();
|
|
196
|
+
const text = requireString(input);
|
|
197
|
+
const policy = toPolicyCallback(options?.policy);
|
|
198
|
+
const formatter = toFormatterCallback(options?.placeholderFormatter);
|
|
199
|
+
let result;
|
|
200
|
+
try {
|
|
201
|
+
result = native.scanAndRedact(text, policy, formatter);
|
|
202
|
+
}
|
|
203
|
+
catch (thrown) {
|
|
204
|
+
throw toSecretScanError(thrown, "DETECTOR_FAILURE");
|
|
205
|
+
}
|
|
206
|
+
return Object.freeze({
|
|
207
|
+
text: result.text,
|
|
208
|
+
findings: toSecretFindings(result.findings),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function createIncrementalSanitizer(options) {
|
|
212
|
+
const native = active();
|
|
213
|
+
let session;
|
|
214
|
+
try {
|
|
215
|
+
session = native.createIncrementalSanitizer(toNativeIncrementalOptions(options));
|
|
216
|
+
}
|
|
217
|
+
catch (thrown) {
|
|
218
|
+
throw toSecretScanError(thrown, "INVALID_LIMITS");
|
|
219
|
+
}
|
|
220
|
+
function run(operation, fallback) {
|
|
221
|
+
let result;
|
|
222
|
+
try {
|
|
223
|
+
result = operation();
|
|
224
|
+
}
|
|
225
|
+
catch (thrown) {
|
|
226
|
+
throw toSecretScanError(thrown, fallback);
|
|
227
|
+
}
|
|
228
|
+
return Object.freeze({
|
|
229
|
+
text: result.text,
|
|
230
|
+
findings: toSecretFindings(result.findings),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return Object.freeze({
|
|
234
|
+
get state() {
|
|
235
|
+
return session.state;
|
|
236
|
+
},
|
|
237
|
+
append: (chunk) => run(() => session.append(requireString(chunk)), "DETECTOR_FAILURE"),
|
|
238
|
+
finalize: () => run(() => session.finalize(), "DETECTOR_FAILURE"),
|
|
239
|
+
abort: () => {
|
|
240
|
+
try {
|
|
241
|
+
session.abort();
|
|
242
|
+
}
|
|
243
|
+
catch (thrown) {
|
|
244
|
+
throw toSecretScanError(thrown, "INVALID_STATE");
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
initialize,
|
|
251
|
+
scan,
|
|
252
|
+
redact,
|
|
253
|
+
scanAndRedact,
|
|
254
|
+
createIncrementalSanitizer,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one runtime this package binds for the host it is running on.
|
|
3
|
+
*
|
|
4
|
+
* `index.ts` and both stream adapters import this module rather than each
|
|
5
|
+
* building their own runtime, so a single `await initialize()` covers the
|
|
6
|
+
* whole public surface no matter which subpath a caller reached first. ESM
|
|
7
|
+
* evaluates this module once, so `dist/index.js` and
|
|
8
|
+
* `dist/adapters/node-stream.js` share the same loaded binding.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here is part of the published API: the package's `exports` map does
|
|
11
|
+
* not reach this module.
|
|
12
|
+
*/
|
|
13
|
+
export declare const runtime: import("./runtime.js").RedactSecretRuntime;
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one runtime this package binds for the host it is running on.
|
|
3
|
+
*
|
|
4
|
+
* `index.ts` and both stream adapters import this module rather than each
|
|
5
|
+
* building their own runtime, so a single `await initialize()` covers the
|
|
6
|
+
* whole public surface no matter which subpath a caller reached first. ESM
|
|
7
|
+
* evaluates this module once, so `dist/index.js` and
|
|
8
|
+
* `dist/adapters/node-stream.js` share the same loaded binding.
|
|
9
|
+
*
|
|
10
|
+
* Nothing here is part of the published API: the package's `exports` map does
|
|
11
|
+
* not reach this module.
|
|
12
|
+
*/
|
|
13
|
+
import { loadNativeBinding } from "#native";
|
|
14
|
+
import { createRedactSecretRuntime } from "./runtime.js";
|
|
15
|
+
export const runtime = createRedactSecretRuntime(loadNativeBinding);
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The documented public type contract of `@redact-secret/core`.
|
|
3
|
+
*
|
|
4
|
+
* Every range in this file is a `[start, end)` pair of **UTF-16 code-unit**
|
|
5
|
+
* offsets into the JavaScript string that produced it, so `input.slice(start,
|
|
6
|
+
* end)` selects exactly the matched span (`decision-define-runtime-bindings`).
|
|
7
|
+
* The Rust core reports UTF-8 byte offsets; the Node and browser bindings
|
|
8
|
+
* convert them without changing the selected span.
|
|
9
|
+
*
|
|
10
|
+
* Custom detector callbacks are deliberately absent: every built-in detector
|
|
11
|
+
* runs in Rust, and the first stable extension surface is a policy and a
|
|
12
|
+
* placeholder formatter, both of which receive safe metadata only.
|
|
13
|
+
*/
|
|
14
|
+
/** The string-index unit every public range in this package uses. */
|
|
15
|
+
export type RangeUnit = "utf16-code-units";
|
|
16
|
+
/** How specific a detector considers a match. */
|
|
17
|
+
export type SecretConfidence = "high" | "medium" | "low";
|
|
18
|
+
/** What a policy decided to do with a finding. */
|
|
19
|
+
export type SecretAction = "redact" | "block" | "warn" | "allow";
|
|
20
|
+
/**
|
|
21
|
+
* A finding before policy evaluation: the safe metadata a policy callback
|
|
22
|
+
* receives. It never carries the input or the matched value.
|
|
23
|
+
*/
|
|
24
|
+
export interface DetectedSecretFinding {
|
|
25
|
+
/** Deterministic finding id (`finding-1`, `finding-2`, ...). */
|
|
26
|
+
readonly id: string;
|
|
27
|
+
/** Finding type, for example `"jwt"` or `"aws_access_key_id"`. */
|
|
28
|
+
readonly type: string;
|
|
29
|
+
/** Id of the detector that produced this finding. */
|
|
30
|
+
readonly detector: string;
|
|
31
|
+
readonly confidence: SecretConfidence;
|
|
32
|
+
/** Start offset, in UTF-16 code units. */
|
|
33
|
+
readonly start: number;
|
|
34
|
+
/** End offset (exclusive), in UTF-16 code units. */
|
|
35
|
+
readonly end: number;
|
|
36
|
+
}
|
|
37
|
+
/** A finding after policy evaluation: the shape {@link scan} returns. */
|
|
38
|
+
export interface SecretFinding extends DetectedSecretFinding {
|
|
39
|
+
readonly action: SecretAction;
|
|
40
|
+
}
|
|
41
|
+
/** Position information passed alongside a finding to a policy. */
|
|
42
|
+
export interface PolicyContext {
|
|
43
|
+
/** Zero-based position in the finalized detection list. */
|
|
44
|
+
readonly findingIndex: number;
|
|
45
|
+
/** Total number of finalized findings for the input. */
|
|
46
|
+
readonly findingCount: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A custom policy. It sees safe metadata only, never the input or a matched
|
|
50
|
+
* value. Omit it to use the built-in policy, which runs in Rust.
|
|
51
|
+
*/
|
|
52
|
+
export interface SecretPolicy {
|
|
53
|
+
evaluate(finding: DetectedSecretFinding, context: PolicyContext): SecretAction;
|
|
54
|
+
}
|
|
55
|
+
/** Position information passed to a placeholder formatter. */
|
|
56
|
+
export interface PlaceholderContext {
|
|
57
|
+
/** One-based position among findings that are actually replaced. */
|
|
58
|
+
readonly placeholderIndex: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A custom placeholder formatter. It sees safe metadata only, and its result
|
|
62
|
+
* is validated: an empty, oversized, or matched-value-reproducing placeholder
|
|
63
|
+
* is rejected.
|
|
64
|
+
*/
|
|
65
|
+
export type PlaceholderFormatter = (finding: SecretFinding, context: PlaceholderContext) => string;
|
|
66
|
+
export interface ScanOptions {
|
|
67
|
+
readonly policy?: SecretPolicy;
|
|
68
|
+
}
|
|
69
|
+
export interface RedactOptions {
|
|
70
|
+
readonly placeholderFormatter?: PlaceholderFormatter;
|
|
71
|
+
}
|
|
72
|
+
export interface ScanAndRedactOptions extends ScanOptions, RedactOptions {
|
|
73
|
+
}
|
|
74
|
+
/** Sanitized text alongside every finding that produced it. */
|
|
75
|
+
export interface ScanResult {
|
|
76
|
+
readonly text: string;
|
|
77
|
+
readonly findings: readonly SecretFinding[];
|
|
78
|
+
}
|
|
79
|
+
/** The terminally distinct lifecycle states of an incremental session. */
|
|
80
|
+
export type IncrementalSanitizerState = "accepting" | "finalized" | "aborted" | "failed";
|
|
81
|
+
/** Position information passed alongside a finding to an incremental policy. */
|
|
82
|
+
export interface IncrementalPolicyContext {
|
|
83
|
+
/** Zero-based position among findings finalized by this session. */
|
|
84
|
+
readonly findingIndex: number;
|
|
85
|
+
}
|
|
86
|
+
/** A custom policy for an incremental session. */
|
|
87
|
+
export interface IncrementalSecretPolicy {
|
|
88
|
+
evaluate(finding: DetectedSecretFinding, context: IncrementalPolicyContext): SecretAction;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Explicit, positive UTF-16 code-unit limits every incremental session
|
|
92
|
+
* requires. There are no environment-derived or silent defaults.
|
|
93
|
+
*/
|
|
94
|
+
export interface IncrementalLimits {
|
|
95
|
+
readonly maxInputCodeUnits: number;
|
|
96
|
+
readonly maxBufferedCodeUnits: number;
|
|
97
|
+
readonly maxTokenCodeUnits: number;
|
|
98
|
+
readonly maxMultilineCodeUnits: number;
|
|
99
|
+
}
|
|
100
|
+
export interface IncrementalSanitizerOptions extends RedactOptions {
|
|
101
|
+
readonly limits: IncrementalLimits;
|
|
102
|
+
readonly policy?: IncrementalSecretPolicy;
|
|
103
|
+
}
|
|
104
|
+
/** Safe output and final findings produced by one session operation. */
|
|
105
|
+
export interface IncrementalSanitizerResult extends ScanResult {
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A bounded incremental session. `append` and `finalize` return only text and
|
|
109
|
+
* findings whose detection window is closed; findings carry absolute UTF-16
|
|
110
|
+
* offsets into the logical whole-session input.
|
|
111
|
+
*/
|
|
112
|
+
export interface IncrementalSanitizer {
|
|
113
|
+
readonly state: IncrementalSanitizerState;
|
|
114
|
+
append(chunk: string): IncrementalSanitizerResult;
|
|
115
|
+
finalize(): IncrementalSanitizerResult;
|
|
116
|
+
abort(): void;
|
|
117
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The documented public type contract of `@redact-secret/core`.
|
|
3
|
+
*
|
|
4
|
+
* Every range in this file is a `[start, end)` pair of **UTF-16 code-unit**
|
|
5
|
+
* offsets into the JavaScript string that produced it, so `input.slice(start,
|
|
6
|
+
* end)` selects exactly the matched span (`decision-define-runtime-bindings`).
|
|
7
|
+
* The Rust core reports UTF-8 byte offsets; the Node and browser bindings
|
|
8
|
+
* convert them without changing the selected span.
|
|
9
|
+
*
|
|
10
|
+
* Custom detector callbacks are deliberately absent: every built-in detector
|
|
11
|
+
* runs in Rust, and the first stable extension surface is a policy and a
|
|
12
|
+
* placeholder formatter, both of which receive safe metadata only.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared product version.
|
|
3
|
+
*
|
|
4
|
+
* The Rust crate, the npm package, the Python package, and the CLI carry one
|
|
5
|
+
* SemVer version per release (`decision-release-bindings-in-lockstep`), so
|
|
6
|
+
* `initialize()` refuses a binding artifact that reports a different one.
|
|
7
|
+
*/
|
|
8
|
+
export declare const VERSION = "0.1.0-beta.1";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared product version.
|
|
3
|
+
*
|
|
4
|
+
* The Rust crate, the npm package, the Python package, and the CLI carry one
|
|
5
|
+
* SemVer version per release (`decision-release-bindings-in-lockstep`), so
|
|
6
|
+
* `initialize()` refuses a binding artifact that reports a different one.
|
|
7
|
+
*/
|
|
8
|
+
export const VERSION = "0.1.0-beta.1";
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@redact-secret/core",
|
|
3
|
+
"description": "Deterministic secret detection and redaction for browser and server JavaScript/TypeScript applications.",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/redact-secret/redact-secret.git",
|
|
8
|
+
"directory": "packages/javascript"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/redact-secret/redact-secret#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/redact-secret/redact-secret/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"secret-detection",
|
|
16
|
+
"redaction",
|
|
17
|
+
"security",
|
|
18
|
+
"browser",
|
|
19
|
+
"node"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./node-stream": {
|
|
34
|
+
"types": "./dist/adapters/node-stream.d.ts",
|
|
35
|
+
"import": "./dist/adapters/node-stream.js"
|
|
36
|
+
},
|
|
37
|
+
"./web-stream": {
|
|
38
|
+
"types": "./dist/adapters/web-stream.d.ts",
|
|
39
|
+
"import": "./dist/adapters/web-stream.js"
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"imports": {
|
|
44
|
+
"#native": {
|
|
45
|
+
"node": "./dist/runtime/node.js",
|
|
46
|
+
"browser": "./dist/runtime/browser.js",
|
|
47
|
+
"default": "./dist/runtime/browser.js"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"types": "./dist/index.d.ts",
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": "20.x || 22.x || 24.x"
|
|
53
|
+
},
|
|
54
|
+
"version": "0.1.0-beta.1",
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@redact-secret/wasm": "0.1.0-beta.1"
|
|
57
|
+
},
|
|
58
|
+
"optionalDependencies": {
|
|
59
|
+
"@redact-secret/node-darwin-arm64": "0.1.0-beta.1",
|
|
60
|
+
"@redact-secret/node-darwin-x64": "0.1.0-beta.1",
|
|
61
|
+
"@redact-secret/node-linux-arm64-gnu": "0.1.0-beta.1",
|
|
62
|
+
"@redact-secret/node-linux-x64-gnu": "0.1.0-beta.1",
|
|
63
|
+
"@redact-secret/node-win32-arm64-msvc": "0.1.0-beta.1",
|
|
64
|
+
"@redact-secret/node-win32-x64-msvc": "0.1.0-beta.1"
|
|
65
|
+
}
|
|
66
|
+
}
|