@redact-secret/adapter-otel-trace 0.1.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.
- package/CHANGELOG.md +48 -0
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +50 -0
- package/dist/span-processor.d.ts +92 -0
- package/dist/span-processor.js +231 -0
- package/package.json +49 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@redact-secret/adapter-otel-trace` are documented in
|
|
4
|
+
this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
|
|
6
|
+
Every package in this repository carries its own SemVer and is released
|
|
7
|
+
independently — see [ARCHITECTURE.md § Versioning](../../ARCHITECTURE.md#versioning).
|
|
8
|
+
A change to the range this package declares against
|
|
9
|
+
`@opentelemetry/sdk-trace-base` or against `@redact-secret/core` is always its
|
|
10
|
+
own entry, naming the test that backs the new range, never folded into a
|
|
11
|
+
generic "bump dependency" line.
|
|
12
|
+
|
|
13
|
+
To release: in a PR into `develop`, move this section's `Unreleased`
|
|
14
|
+
entries under a new `## [x.y.z] - YYYY-MM-DD` heading matching the version
|
|
15
|
+
bumped in `package.json`. The next release train publishes and tags every
|
|
16
|
+
package whose declared version isn't on its registry yet — see
|
|
17
|
+
[RELEASING.md](../../RELEASING.md). This file ships inside the published
|
|
18
|
+
tarball (`files` in `package.json`), so a consumer can read it from
|
|
19
|
+
`node_modules` without leaving their editor.
|
|
20
|
+
|
|
21
|
+
This package's code shipped as `@redact-secret/adapter-otel` `0.1.0` to
|
|
22
|
+
`0.1.2`; that history is in
|
|
23
|
+
[`packages/adapter-otel/CHANGELOG.md`](../adapter-otel/CHANGELOG.md). Its
|
|
24
|
+
version numbers start again here, at `0.1.0`, and do not follow the old name's.
|
|
25
|
+
|
|
26
|
+
## [Unreleased]
|
|
27
|
+
|
|
28
|
+
## [0.1.0] - 2026-09-30
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- The package, under a name that says what it covers: OpenTelemetry JS
|
|
32
|
+
**traces** (redact-secret/redact-secret-adapters#49). It is the code
|
|
33
|
+
`@redact-secret/adapter-otel` `0.1.2` shipped, unchanged: the same
|
|
34
|
+
`createRedactingSpanProcessor`, `RedactingSpanProcessorWith`,
|
|
35
|
+
`redactAttributesWith`, options (`policy`, `maxStringLength`, `onOutcome`,
|
|
36
|
+
and `pii` on the live factory) and outcome shape, and the same
|
|
37
|
+
`REDACT_SECRET_SPAN_DROPPED` warning, whose message still begins
|
|
38
|
+
`@redact-secret/adapter-otel:` so a filter written against it keeps
|
|
39
|
+
matching. It protects spans only — the span name, attributes, events, status
|
|
40
|
+
message and link attributes. OpenTelemetry Logs (`LogRecord`s) pass through
|
|
41
|
+
no code in this package.
|
|
42
|
+
- Declares `@opentelemetry/sdk-trace-base` `^2.0.0` and `@redact-secret/core`
|
|
43
|
+
`^0.1.0-beta.6`, the ranges `adapter-otel` `0.1.2` declared, backed by the
|
|
44
|
+
same real-host tests (`test/otel-host.test.ts`, `test/otel-lifecycle.test.ts`,
|
|
45
|
+
`test/outcome.test.ts`, `test/vault-token.test.ts`), which CI runs at both
|
|
46
|
+
ends of each range. `test/exporter-bytes.test.ts` adds the final OTLP JSON
|
|
47
|
+
bytes an exporter would send, for the injected and the live factory, through
|
|
48
|
+
both `SimpleSpanProcessor` and `BatchSpanProcessor`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Omiologic
|
|
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,218 @@
|
|
|
1
|
+
# @redact-secret/adapter-otel-trace
|
|
2
|
+
|
|
3
|
+
A redacting OpenTelemetry JS `SpanProcessor` for **traces**, over the
|
|
4
|
+
[Redact Secret](https://github.com/redact-secret/redact-secret) core.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
import { NodeTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
8
|
+
import { createRedactingSpanProcessor } from "@redact-secret/adapter-otel-trace";
|
|
9
|
+
|
|
10
|
+
const provider = new NodeTracerProvider({
|
|
11
|
+
spanProcessors: [await createRedactingSpanProcessor(new BatchSpanProcessor(exporter))],
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## What it covers, and what it does not
|
|
16
|
+
|
|
17
|
+
**Spans only.** This package plugs into the tracing pipeline and sees what a
|
|
18
|
+
`SpanProcessor` sees. It does **not** protect OpenTelemetry **Logs**: a
|
|
19
|
+
`LogRecord` emitted through `@opentelemetry/sdk-logs` (or a log bridge such as
|
|
20
|
+
the pino or winston instrumentation) never passes through it and reaches its
|
|
21
|
+
exporter as it was written. There is no OpenTelemetry Logs adapter yet;
|
|
22
|
+
`@redact-secret/adapter-otel-logs` is a reserved name for one, not a package.
|
|
23
|
+
Metrics are not covered either.
|
|
24
|
+
|
|
25
|
+
This package was published as
|
|
26
|
+
[`@redact-secret/adapter-otel`](https://www.npmjs.com/package/@redact-secret/adapter-otel) up to `0.1.2`. That name keeps working — its
|
|
27
|
+
releases after `0.1.2` re-export this package — but new code should import
|
|
28
|
+
from here; see
|
|
29
|
+
[Migrating from `@redact-secret/adapter-otel`](#migrating-from-redact-secretadapter-otel).
|
|
30
|
+
|
|
31
|
+
<!-- smoke-test:example -->
|
|
32
|
+
```js
|
|
33
|
+
import { BasicTracerProvider, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
34
|
+
import { JsonTraceSerializer } from "@opentelemetry/otlp-transformer";
|
|
35
|
+
import { createRedactingSpanProcessor } from "@redact-secret/adapter-otel-trace";
|
|
36
|
+
|
|
37
|
+
// Synthetic, revoked-shaped values only — never a real credential.
|
|
38
|
+
const token = "ghp_SYNTHETICREVOKED00000000000000000000";
|
|
39
|
+
|
|
40
|
+
// Writes the OTLP/JSON request body an OTLP http/json exporter would send.
|
|
41
|
+
const exporter = {
|
|
42
|
+
export(spans, done) {
|
|
43
|
+
process.stdout.write(`${new TextDecoder().decode(JsonTraceSerializer.serializeRequest(spans))}\n`);
|
|
44
|
+
done({ code: 0 });
|
|
45
|
+
},
|
|
46
|
+
shutdown: async () => {},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const provider = new BasicTracerProvider({
|
|
50
|
+
spanProcessors: [await createRedactingSpanProcessor(new SimpleSpanProcessor(exporter))],
|
|
51
|
+
});
|
|
52
|
+
const span = provider.getTracer("checkout").startSpan(`deploy ${token}`);
|
|
53
|
+
span.setAttribute("llm.input_messages", `deploy with token ${token}`);
|
|
54
|
+
span.addEvent("tool_call", { "tool.args": `Bearer ${token}` });
|
|
55
|
+
span.end();
|
|
56
|
+
await provider.shutdown();
|
|
57
|
+
// ...{"name":"deploy <SECRET_1>",...,"attributes":[{"key":"llm.input_messages","value":{"stringValue":"deploy with token <SECRET_1>"}}],...
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The clean-install smoke test (`npm run smoke-test`) runs this block verbatim
|
|
61
|
+
from a throwaway project outside the repository, against the real core, and
|
|
62
|
+
inspects the exporter's bytes.
|
|
63
|
+
|
|
64
|
+
## What is redacted
|
|
65
|
+
|
|
66
|
+
In `onEnd`, before the span reaches the next processor, the processor redacts
|
|
67
|
+
the span name, every string and string-array attribute (a `null` hole in an
|
|
68
|
+
array is kept in place), every event's name and attributes, the status message,
|
|
69
|
+
and every link's attributes. Attribute names are not
|
|
70
|
+
allowlisted, so OpenInference (`llm.input_messages`, `input.value`, …) and GenAI
|
|
71
|
+
semantic-convention attributes (`gen_ai.prompt`, …) are covered without
|
|
72
|
+
hardcoding either convention.
|
|
73
|
+
|
|
74
|
+
## The load-bearing assumption
|
|
75
|
+
|
|
76
|
+
`ReadableSpan`'s fields are typed `readonly` but are plain writable objects at
|
|
77
|
+
runtime, and this processor writes the masked values back in place. Every write
|
|
78
|
+
is read back; if one does not take (for example, an earlier processor froze the
|
|
79
|
+
attributes), the span is **dropped** — not exported, and nothing is thrown out
|
|
80
|
+
of `span.end()` — and a one-time process warning
|
|
81
|
+
(`REDACT_SECRET_SPAN_DROPPED`) names the field, never its value.
|
|
82
|
+
`test/otel-host.test.ts` builds a real span, passes it through a real
|
|
83
|
+
`BasicTracerProvider`, and asserts the exporter saw redacted fields. That
|
|
84
|
+
assertion is not optional.
|
|
85
|
+
|
|
86
|
+
## Exports
|
|
87
|
+
|
|
88
|
+
| Export | Purpose |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| `createRedactingSpanProcessor(next, options?)` | Live: awaits the core's `initialize()`, wraps `next` |
|
|
91
|
+
| `RedactingSpanProcessorWith` | `new (next, scanAndRedact, options?)` — injected scanner |
|
|
92
|
+
| `redactAttributesWith(scanAndRedact, attributes, options?)` | Mutates one attribute bag in place; throws a `TypeError` (naming no value) if it cannot |
|
|
93
|
+
|
|
94
|
+
`options` is `{ policy, maxStringLength }` — `MaskLeafOptions`, re-exported
|
|
95
|
+
here — plus `onOutcome` and, on the live factory, `pii` — every option
|
|
96
|
+
`adapter-otel` `0.1.2` had. The older `RedactAttributesOptions` alias is deprecated. See
|
|
97
|
+
[`@redact-secret/adapter`](../adapter#fail-closed-markers) for the markers.
|
|
98
|
+
|
|
99
|
+
**Attribute names are not scanned.** The processor masks attribute *values*
|
|
100
|
+
(and the span name, event names, and status message). Every attribute key,
|
|
101
|
+
on the span, its events, and its links, reaches the exporter unchanged, so an
|
|
102
|
+
attribute *named* after a secret keeps that name. Do not put a secret in an
|
|
103
|
+
attribute key.
|
|
104
|
+
|
|
105
|
+
## PII detection is opt-in
|
|
106
|
+
|
|
107
|
+
The core detects credentials out of the box; PII detection is a
|
|
108
|
+
separate activation, and it is process-wide and one-shot — the first selection
|
|
109
|
+
wins, and a later *different* one fails with `PII_ACTIVATION_CONFLICT`.
|
|
110
|
+
|
|
111
|
+
Either order works. Activate it yourself before building the provider:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
await initialize({ pii: ["pii:global"] });
|
|
115
|
+
const processor = await createRedactingSpanProcessor(next); // accepted, not fought over
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
or let the factory do it, which is the order to prefer when this adapter is the
|
|
119
|
+
first thing in the process to touch the core:
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
const processor = await createRedactingSpanProcessor(next, { pii: ["pii:global"] });
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
When you pass `pii`, the factory reads the core's `piiActivation()` afterwards
|
|
126
|
+
and **rejects** if the active selection is not the one you asked for, rather
|
|
127
|
+
than returning a processor that scans with PII silently off. The rejection
|
|
128
|
+
carries a fixed `code` — `PII_ACTIVATION_NOT_ACTIVE`, or
|
|
129
|
+
`PII_ACTIVATION_UNSUPPORTED` against a core too old to report an activation —
|
|
130
|
+
and never echoes a selector, the input, or the core's own message. Omitting
|
|
131
|
+
`pii` needs no newer core: the declared `@redact-secret/core` range is
|
|
132
|
+
unchanged, and every other initialization failure still rejects exactly as it
|
|
133
|
+
did.
|
|
134
|
+
|
|
135
|
+
**Activation is not masking.** Under the core's default policy, PII types are
|
|
136
|
+
confidence-gated rather than always redacted: a `High`-confidence finding
|
|
137
|
+
redacts, while `Medium` and `Low` resolve to `warn` — and a `warn` finding
|
|
138
|
+
leaves the text alone. Enabling PII therefore still lets lower-confidence PII
|
|
139
|
+
reach the exporter as plaintext. Pass your own `policy` mapping those findings
|
|
140
|
+
to `redact` if you need them masked; this package decides nothing about policy.
|
|
141
|
+
The counters below make it visible: a span whose `values.findings` is non-zero
|
|
142
|
+
while `values.redacted` stays at zero is exactly this case.
|
|
143
|
+
|
|
144
|
+
## Counting what happened
|
|
145
|
+
|
|
146
|
+
`onOutcome` reports one summary per **span**. It is
|
|
147
|
+
observational: increment your own counters from it. This package creates no
|
|
148
|
+
exporter or network client for you.
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
const processor = await createRedactingSpanProcessor(new BatchSpanProcessor(exporter), {
|
|
152
|
+
onOutcome: ({ values, dropped }) => {
|
|
153
|
+
metrics.increment("span.redacted_values", values.redacted);
|
|
154
|
+
if (dropped) metrics.increment("span.dropped_unredactable");
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
{ host: "otel", unit: "span",
|
|
161
|
+
values: { scanned, findings, redacted, blocked, limited, failed },
|
|
162
|
+
dropped: false }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The counts are defined in
|
|
166
|
+
[`@redact-secret/adapter`](../adapter#outcome-counters) — `findings` is not a
|
|
167
|
+
count of distinct credentials, and `redacted` is lower than `findings` whenever
|
|
168
|
+
a finding leaves text alone. Every string attribute, array element, event name
|
|
169
|
+
and status message is its own counted leaf; attribute *names* are not scanned
|
|
170
|
+
and not counted.
|
|
171
|
+
|
|
172
|
+
`dropped` is **this processor's** decision: it did not hand the span to the
|
|
173
|
+
next processor because a masked value would not write back (see the
|
|
174
|
+
load-bearing assumption above). It does not mean the span was sampled out, and
|
|
175
|
+
`dropped: false` does **not** mean the span was exported — whether the next
|
|
176
|
+
processor kept it and whether an exporter succeeded are things this adapter
|
|
177
|
+
never learns and does not report.
|
|
178
|
+
|
|
179
|
+
- The observer runs once the span has been forwarded or dropped, so it cannot
|
|
180
|
+
change what is exported, and anything it throws is swallowed and never read.
|
|
181
|
+
- It is re-entrancy- and thread-guarded: an observer that ends another span
|
|
182
|
+
does not recurse, and one thread's report never suppresses another's.
|
|
183
|
+
|
|
184
|
+
## Supported SDK versions
|
|
185
|
+
|
|
186
|
+
`@opentelemetry/sdk-trace-base ^2.0.0` and `@redact-secret/core`
|
|
187
|
+
`^0.1.0-beta.6`. CI runs the real-host tests at both ends of each range. The
|
|
188
|
+
SDK is imported as types only — it never enters this package's runtime graph.
|
|
189
|
+
|
|
190
|
+
## Migrating from `@redact-secret/adapter-otel`
|
|
191
|
+
|
|
192
|
+
`0.1.0` of this package is the code `@redact-secret/adapter-otel` `0.1.2`
|
|
193
|
+
shipped: the same exports, options, outcome shape, peer ranges and fail-closed
|
|
194
|
+
markers. Only the name changes, so migrating is a dependency swap and an import
|
|
195
|
+
specifier:
|
|
196
|
+
|
|
197
|
+
```sh
|
|
198
|
+
npm uninstall @redact-secret/adapter-otel
|
|
199
|
+
npm install @redact-secret/adapter-otel-trace
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
```diff
|
|
203
|
+
-import { createRedactingSpanProcessor } from "@redact-secret/adapter-otel";
|
|
204
|
+
+import { createRedactingSpanProcessor } from "@redact-secret/adapter-otel-trace";
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Nothing breaks if you do not migrate yet. `@redact-secret/adapter-otel`
|
|
208
|
+
`0.1.2` is this code under the old name, and its later releases re-export this
|
|
209
|
+
package, so both names hand out the same functions and the same
|
|
210
|
+
`RedactingSpanProcessorWith` class and mixing them in one process is safe.
|
|
211
|
+
Those later releases mark every export `@deprecated`, which editors show as a
|
|
212
|
+
strikethrough.
|
|
213
|
+
|
|
214
|
+
Neither name protects OpenTelemetry Logs, before or after migrating.
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The live OpenTelemetry JS **trace** integration: a `SpanProcessor`. It does
|
|
3
|
+
* not see OpenTelemetry Logs (`LogRecord`s); nothing in this package protects
|
|
4
|
+
* them. It was published as `@redact-secret/adapter-otel` up to `0.1.2`; that
|
|
5
|
+
* name now re-exports this package.
|
|
6
|
+
*
|
|
7
|
+
* ```js
|
|
8
|
+
* import { NodeTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
9
|
+
* import { createRedactingSpanProcessor } from "@redact-secret/adapter-otel-trace";
|
|
10
|
+
*
|
|
11
|
+
* const provider = new NodeTracerProvider({
|
|
12
|
+
* spanProcessors: [await createRedactingSpanProcessor(new BatchSpanProcessor(exporter))],
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* `await initialize()` must resolve before `scanAndRedact` is used; this
|
|
17
|
+
* factory enforces that order. It is the only code in the package that loads
|
|
18
|
+
* `@redact-secret/core` at runtime, and does so on call (as
|
|
19
|
+
* `@redact-secret/adapter`'s `createMaskSecrets` does), so importing the
|
|
20
|
+
* injected API never loads the native core.
|
|
21
|
+
*
|
|
22
|
+
* PII detection is opt-in and process-wide in the core. Omit `pii` and this
|
|
23
|
+
* factory initializes the core as before and accepts whatever selection the
|
|
24
|
+
* application already activated, in either order. Pass
|
|
25
|
+
* `createRedactingSpanProcessor(next, { pii: ["pii:global"] })` to activate a
|
|
26
|
+
* selection from here instead, and the factory rejects rather than run with
|
|
27
|
+
* PII off. See `@redact-secret/adapter`'s `activateCore` for the whole rule,
|
|
28
|
+
* including why activation is not the same as masking every PII value.
|
|
29
|
+
*/
|
|
30
|
+
import type { SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
31
|
+
import { type CoreActivation } from "@redact-secret/adapter";
|
|
32
|
+
import { type RedactingSpanProcessorOptions, RedactingSpanProcessorWith } from "./span-processor.js";
|
|
33
|
+
export type { CoreActivation, MaskLeafOptions } from "@redact-secret/adapter";
|
|
34
|
+
export { type OtelSpanOutcome, type RedactAttributesOptions, type RedactingSpanProcessorOptions, RedactingSpanProcessorWith, redactAttributesWith, } from "./span-processor.js";
|
|
35
|
+
/** {@link RedactingSpanProcessorOptions} plus the live factory's PII activation. */
|
|
36
|
+
export type CreateRedactingSpanProcessorOptions = RedactingSpanProcessorOptions & CoreActivation;
|
|
37
|
+
/**
|
|
38
|
+
* Awaits `initialize()` once, then wraps `next` with the real scanner.
|
|
39
|
+
* `options.onOutcome` reports one input-free summary per span.
|
|
40
|
+
*
|
|
41
|
+
* `options.pii` activates core PII selectors for the whole process. Omit it
|
|
42
|
+
* and an activation the application already made is accepted rather than
|
|
43
|
+
* fought over. Pass it and this rejects — with a fixed message and code, never
|
|
44
|
+
* a selector or a core message — rather than run with PII off.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createRedactingSpanProcessor(next: SpanProcessor, options?: CreateRedactingSpanProcessorOptions): Promise<RedactingSpanProcessorWith>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The live OpenTelemetry JS **trace** integration: a `SpanProcessor`. It does
|
|
3
|
+
* not see OpenTelemetry Logs (`LogRecord`s); nothing in this package protects
|
|
4
|
+
* them. It was published as `@redact-secret/adapter-otel` up to `0.1.2`; that
|
|
5
|
+
* name now re-exports this package.
|
|
6
|
+
*
|
|
7
|
+
* ```js
|
|
8
|
+
* import { NodeTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
9
|
+
* import { createRedactingSpanProcessor } from "@redact-secret/adapter-otel-trace";
|
|
10
|
+
*
|
|
11
|
+
* const provider = new NodeTracerProvider({
|
|
12
|
+
* spanProcessors: [await createRedactingSpanProcessor(new BatchSpanProcessor(exporter))],
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* `await initialize()` must resolve before `scanAndRedact` is used; this
|
|
17
|
+
* factory enforces that order. It is the only code in the package that loads
|
|
18
|
+
* `@redact-secret/core` at runtime, and does so on call (as
|
|
19
|
+
* `@redact-secret/adapter`'s `createMaskSecrets` does), so importing the
|
|
20
|
+
* injected API never loads the native core.
|
|
21
|
+
*
|
|
22
|
+
* PII detection is opt-in and process-wide in the core. Omit `pii` and this
|
|
23
|
+
* factory initializes the core as before and accepts whatever selection the
|
|
24
|
+
* application already activated, in either order. Pass
|
|
25
|
+
* `createRedactingSpanProcessor(next, { pii: ["pii:global"] })` to activate a
|
|
26
|
+
* selection from here instead, and the factory rejects rather than run with
|
|
27
|
+
* PII off. See `@redact-secret/adapter`'s `activateCore` for the whole rule,
|
|
28
|
+
* including why activation is not the same as masking every PII value.
|
|
29
|
+
*/
|
|
30
|
+
import { activateCore } from "@redact-secret/adapter";
|
|
31
|
+
import { RedactingSpanProcessorWith } from "./span-processor.js";
|
|
32
|
+
export { RedactingSpanProcessorWith, redactAttributesWith, } from "./span-processor.js";
|
|
33
|
+
/**
|
|
34
|
+
* Awaits `initialize()` once, then wraps `next` with the real scanner.
|
|
35
|
+
* `options.onOutcome` reports one input-free summary per span.
|
|
36
|
+
*
|
|
37
|
+
* `options.pii` activates core PII selectors for the whole process. Omit it
|
|
38
|
+
* and an activation the application already made is accepted rather than
|
|
39
|
+
* fought over. Pass it and this rejects — with a fixed message and code, never
|
|
40
|
+
* a selector or a core message — rather than run with PII off.
|
|
41
|
+
*/
|
|
42
|
+
export async function createRedactingSpanProcessor(next, options = {}) {
|
|
43
|
+
// `pii` is read by property so an inherited activation survives, and
|
|
44
|
+
// `options` is forwarded as the same object so every other inherited key
|
|
45
|
+
// does too. `pii` rides along unread, as any unknown key would.
|
|
46
|
+
const activation = options.pii === undefined ? {} : { pii: options.pii };
|
|
47
|
+
const loaded = await import("@redact-secret/core");
|
|
48
|
+
await activateCore(loaded, activation);
|
|
49
|
+
return new RedactingSpanProcessorWith(next, loaded.scanAndRedact, options);
|
|
50
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `SpanProcessor` (OpenTelemetry JS, pinned against
|
|
3
|
+
* `@opentelemetry/sdk-trace-base@2.11.0`:
|
|
4
|
+
* https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/sdk-trace/src/SpanProcessor.ts)
|
|
5
|
+
* that redacts the span name, every string and string-array attribute —
|
|
6
|
+
* including OpenInference (`llm.input_messages`, `input.value`, ...) and
|
|
7
|
+
* GenAI semantic-convention attributes (`gen_ai.prompt`, ...) — each event's
|
|
8
|
+
* name and attributes, the status message, and each link's attributes,
|
|
9
|
+
* before handing the span to the next processor. It does not
|
|
10
|
+
* allowlist those attribute names: every string-shaped attribute value is
|
|
11
|
+
* scanned, which covers any semantic convention without hardcoding it and
|
|
12
|
+
* without a dependency on either convention's attribute list.
|
|
13
|
+
*
|
|
14
|
+
* `ReadableSpan`'s fields are typed `readonly` but are plain, writable
|
|
15
|
+
* objects at runtime, and `onEnd` writes the masked values back in place.
|
|
16
|
+
* Every write is read back. If one does not take (a frozen bag, a setter
|
|
17
|
+
* that ignores it), the span is dropped — never forwarded unredacted —
|
|
18
|
+
* with a one-time process warning naming the field, never its value.
|
|
19
|
+
* `test/otel-host.test.ts` asserts the writes take effect on a real span,
|
|
20
|
+
* at both ends of the declared SDK range.
|
|
21
|
+
*
|
|
22
|
+
* This file never imports `@opentelemetry/sdk-trace-base` at runtime — the
|
|
23
|
+
* imports below are `import type`, erased at compile time. A
|
|
24
|
+
* `SpanProcessor` is a structural (duck-typed) interface in JS, so
|
|
25
|
+
* wrapping one needs no dependency, and this module is testable with a
|
|
26
|
+
* plain object.
|
|
27
|
+
*/
|
|
28
|
+
import type { ReadableSpan, Span, SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
29
|
+
import type { MaskLeafOptions, ScanAndRedact, ValueCounts } from "@redact-secret/adapter";
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Use `MaskLeafOptions` (`{ policy, maxStringLength }`), which
|
|
32
|
+
* this package re-exports; this alias adds nothing and will be removed in a
|
|
33
|
+
* future major version.
|
|
34
|
+
*/
|
|
35
|
+
export type RedactAttributesOptions = MaskLeafOptions;
|
|
36
|
+
/**
|
|
37
|
+
* One summary per **span**, the unit a host counts in
|
|
38
|
+
* (redact-secret/redact-secret-adapters#45). Every field is bounded and
|
|
39
|
+
* enumerated; `values`' definitions are in `@redact-secret/adapter`'s
|
|
40
|
+
* `outcome.ts`. No attribute name, key, value or error text is in it.
|
|
41
|
+
*/
|
|
42
|
+
export interface OtelSpanOutcome {
|
|
43
|
+
readonly host: "otel";
|
|
44
|
+
readonly unit: "span";
|
|
45
|
+
readonly values: ValueCounts;
|
|
46
|
+
/**
|
|
47
|
+
* `true` when **this processor** did not hand the span to the next one,
|
|
48
|
+
* because a masked value would not write back. It does not mean the span
|
|
49
|
+
* was sampled out, and `false` does not mean the span was exported: whether
|
|
50
|
+
* the next processor kept it and whether an exporter succeeded are things
|
|
51
|
+
* this adapter never learns and does not report.
|
|
52
|
+
*/
|
|
53
|
+
readonly dropped: boolean;
|
|
54
|
+
}
|
|
55
|
+
export interface RedactingSpanProcessorOptions extends MaskLeafOptions {
|
|
56
|
+
/**
|
|
57
|
+
* Observational: called once per span, synchronously at the end of `onEnd`,
|
|
58
|
+
* after the span has either been forwarded or dropped. Increment your own
|
|
59
|
+
* counters from it.
|
|
60
|
+
*
|
|
61
|
+
* It cannot change what is exported, and anything it throws is swallowed,
|
|
62
|
+
* never read, and never rethrown — including for a span that was dropped.
|
|
63
|
+
* It is re-entrancy-guarded. No exporter or network client is created for it.
|
|
64
|
+
*/
|
|
65
|
+
readonly onOutcome?: (outcome: OtelSpanOutcome) => void;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Mutates `attributes` in place. A no-op for `undefined`/`null`. Throws a
|
|
69
|
+
* `TypeError` naming no value if a masked value cannot be written back.
|
|
70
|
+
*/
|
|
71
|
+
export declare function redactAttributesWith(scanAndRedact: ScanAndRedact, attributes: object | null | undefined, options?: MaskLeafOptions): void;
|
|
72
|
+
/**
|
|
73
|
+
* Wraps `next` (any object shaped like a `SpanProcessor`) and redacts each
|
|
74
|
+
* span's free text before delegating to it. `scanAndRedact` is injected so
|
|
75
|
+
* this class is testable without the built native addon; see `./index.ts`
|
|
76
|
+
* for the live factory.
|
|
77
|
+
*/
|
|
78
|
+
export declare class RedactingSpanProcessorWith implements SpanProcessor {
|
|
79
|
+
#private;
|
|
80
|
+
constructor(next: SpanProcessor, scanAndRedact: ScanAndRedact, options?: RedactingSpanProcessorOptions);
|
|
81
|
+
onStart(...args: Parameters<SpanProcessor["onStart"]>): void;
|
|
82
|
+
/**
|
|
83
|
+
* Forwards the SDK's optional, experimental `onEnding` (called while the
|
|
84
|
+
* span is still writable). Typed structurally: it is absent from the
|
|
85
|
+
* `SpanProcessor` interface at the low end of the declared SDK range.
|
|
86
|
+
*/
|
|
87
|
+
onEnding(span: Span): void;
|
|
88
|
+
/** Never throws: a span that cannot be redacted is dropped, not exported. */
|
|
89
|
+
onEnd(span: ReadableSpan): void;
|
|
90
|
+
shutdown(): Promise<void>;
|
|
91
|
+
forceFlush(): Promise<void>;
|
|
92
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `SpanProcessor` (OpenTelemetry JS, pinned against
|
|
3
|
+
* `@opentelemetry/sdk-trace-base@2.11.0`:
|
|
4
|
+
* https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/sdk-trace/src/SpanProcessor.ts)
|
|
5
|
+
* that redacts the span name, every string and string-array attribute —
|
|
6
|
+
* including OpenInference (`llm.input_messages`, `input.value`, ...) and
|
|
7
|
+
* GenAI semantic-convention attributes (`gen_ai.prompt`, ...) — each event's
|
|
8
|
+
* name and attributes, the status message, and each link's attributes,
|
|
9
|
+
* before handing the span to the next processor. It does not
|
|
10
|
+
* allowlist those attribute names: every string-shaped attribute value is
|
|
11
|
+
* scanned, which covers any semantic convention without hardcoding it and
|
|
12
|
+
* without a dependency on either convention's attribute list.
|
|
13
|
+
*
|
|
14
|
+
* `ReadableSpan`'s fields are typed `readonly` but are plain, writable
|
|
15
|
+
* objects at runtime, and `onEnd` writes the masked values back in place.
|
|
16
|
+
* Every write is read back. If one does not take (a frozen bag, a setter
|
|
17
|
+
* that ignores it), the span is dropped — never forwarded unredacted —
|
|
18
|
+
* with a one-time process warning naming the field, never its value.
|
|
19
|
+
* `test/otel-host.test.ts` asserts the writes take effect on a real span,
|
|
20
|
+
* at both ends of the declared SDK range.
|
|
21
|
+
*
|
|
22
|
+
* This file never imports `@opentelemetry/sdk-trace-base` at runtime — the
|
|
23
|
+
* imports below are `import type`, erased at compile time. A
|
|
24
|
+
* `SpanProcessor` is a structural (duck-typed) interface in JS, so
|
|
25
|
+
* wrapping one needs no dependency, and this module is testable with a
|
|
26
|
+
* plain object.
|
|
27
|
+
*/
|
|
28
|
+
import { countLeaf, createOutcomeCounter, ERROR_MARKER, maskLeafOutcomeWith, notify, toValueCounts, } from "@redact-secret/adapter";
|
|
29
|
+
/** A span field that did not take a masked write. The message names the field, never its value. */
|
|
30
|
+
class UnredactableFieldError extends Error {
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `counter` is read on every call, not captured, so one masker serves every
|
|
34
|
+
* span and the processor can swap in a fresh per-span counter.
|
|
35
|
+
*/
|
|
36
|
+
function maskerFor(scanAndRedact, options, counter) {
|
|
37
|
+
return (text) => {
|
|
38
|
+
try {
|
|
39
|
+
const leaf = maskLeafOutcomeWith(scanAndRedact, text, options);
|
|
40
|
+
if (counter !== undefined)
|
|
41
|
+
countLeaf(counter(), leaf);
|
|
42
|
+
return leaf.text;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
if (counter !== undefined)
|
|
46
|
+
counter().failed += 1;
|
|
47
|
+
return ERROR_MARKER;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** The masked value, or `value` itself when nothing in it changed. */
|
|
52
|
+
function maskAttributeValue(mask, value) {
|
|
53
|
+
if (typeof value === "string")
|
|
54
|
+
return mask(value);
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
// OpenTelemetry allows null/undefined holes in a homogeneous array, so
|
|
57
|
+
// every string element is masked and every other element kept in place.
|
|
58
|
+
const masked = value.map((item) => (typeof item === "string" ? mask(item) : item));
|
|
59
|
+
return masked.some((item, index) => item !== value[index]) ? masked : value;
|
|
60
|
+
}
|
|
61
|
+
// Numbers and booleans cannot carry a secret as free text.
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
/** Writes `value` to `target[key]` if it differs, and throws if the write does not show. */
|
|
65
|
+
function writeBack(target, key, value, field) {
|
|
66
|
+
const record = target;
|
|
67
|
+
if (record[key] === value)
|
|
68
|
+
return;
|
|
69
|
+
try {
|
|
70
|
+
record[key] = value;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
throw new UnredactableFieldError(`${field} did not take the masked write`);
|
|
74
|
+
}
|
|
75
|
+
if (record[key] !== value)
|
|
76
|
+
throw new UnredactableFieldError(`${field} did not take the masked write`);
|
|
77
|
+
}
|
|
78
|
+
function redactBag(mask, bag, field) {
|
|
79
|
+
if (bag == null)
|
|
80
|
+
return;
|
|
81
|
+
for (const key of Object.keys(bag)) {
|
|
82
|
+
writeBack(bag, key, maskAttributeValue(mask, bag[key]), field);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Mutates `attributes` in place. A no-op for `undefined`/`null`. Throws a
|
|
87
|
+
* `TypeError` naming no value if a masked value cannot be written back.
|
|
88
|
+
*/
|
|
89
|
+
export function redactAttributesWith(scanAndRedact, attributes, options = {}) {
|
|
90
|
+
try {
|
|
91
|
+
redactBag(maskerFor(scanAndRedact, options), attributes, "attributes");
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
throw new TypeError("redactAttributesWith: a masked attribute could not be written back");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function redactSpan(mask, span) {
|
|
98
|
+
if (typeof span.name === "string")
|
|
99
|
+
writeBack(span, "name", mask(span.name), "span.name");
|
|
100
|
+
redactBag(mask, span.attributes, "span.attributes");
|
|
101
|
+
const status = span.status;
|
|
102
|
+
if (typeof status?.message === "string") {
|
|
103
|
+
const message = mask(status.message);
|
|
104
|
+
// Replaced, not mutated: the status object may be the caller's own.
|
|
105
|
+
if (message !== status.message)
|
|
106
|
+
writeBack(span, "status", { ...status, message }, "span.status");
|
|
107
|
+
}
|
|
108
|
+
for (const [index, event] of (span.events ?? []).entries()) {
|
|
109
|
+
if (typeof event.name === "string")
|
|
110
|
+
writeBack(event, "name", mask(event.name), `span.events[${index}].name`);
|
|
111
|
+
redactBag(mask, event.attributes, `span.events[${index}].attributes`);
|
|
112
|
+
}
|
|
113
|
+
for (const [index, link] of (span.links ?? []).entries()) {
|
|
114
|
+
redactBag(mask, link.attributes, `span.links[${index}].attributes`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Wraps `next` (any object shaped like a `SpanProcessor`) and redacts each
|
|
119
|
+
* span's free text before delegating to it. `scanAndRedact` is injected so
|
|
120
|
+
* this class is testable without the built native addon; see `./index.ts`
|
|
121
|
+
* for the live factory.
|
|
122
|
+
*/
|
|
123
|
+
export class RedactingSpanProcessorWith {
|
|
124
|
+
#next;
|
|
125
|
+
#mask;
|
|
126
|
+
#onOutcome;
|
|
127
|
+
#counter = createOutcomeCounter();
|
|
128
|
+
#reporting = false;
|
|
129
|
+
#warned = false;
|
|
130
|
+
constructor(next, scanAndRedact, options = {}) {
|
|
131
|
+
if (typeof next?.onEnd !== "function") {
|
|
132
|
+
throw new TypeError("RedactingSpanProcessorWith: next must be a SpanProcessor");
|
|
133
|
+
}
|
|
134
|
+
if (typeof scanAndRedact !== "function") {
|
|
135
|
+
throw new TypeError("RedactingSpanProcessorWith: scanAndRedact must be a function");
|
|
136
|
+
}
|
|
137
|
+
const { onOutcome, ...maskOptions } = options;
|
|
138
|
+
if (onOutcome !== undefined && typeof onOutcome !== "function") {
|
|
139
|
+
throw new TypeError("RedactingSpanProcessorWith: onOutcome must be a function");
|
|
140
|
+
}
|
|
141
|
+
this.#next = next;
|
|
142
|
+
this.#onOutcome = onOutcome;
|
|
143
|
+
this.#mask = maskerFor(scanAndRedact, maskOptions, onOutcome === undefined ? undefined : () => this.#counter);
|
|
144
|
+
}
|
|
145
|
+
onStart(...args) {
|
|
146
|
+
this.#next.onStart?.(...args);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Forwards the SDK's optional, experimental `onEnding` (called while the
|
|
150
|
+
* span is still writable). Typed structurally: it is absent from the
|
|
151
|
+
* `SpanProcessor` interface at the low end of the declared SDK range.
|
|
152
|
+
*/
|
|
153
|
+
onEnding(span) {
|
|
154
|
+
this.#next.onEnding?.(span);
|
|
155
|
+
}
|
|
156
|
+
/** Never throws: a span that cannot be redacted is dropped, not exported. */
|
|
157
|
+
onEnd(span) {
|
|
158
|
+
const counting = this.#onOutcome !== undefined;
|
|
159
|
+
// A fresh counter per span, so `onOutcome` reports this span's values and
|
|
160
|
+
// not a running total, and the previous one is restored: a downstream
|
|
161
|
+
// processor may end a span synchronously inside `#next.onEnd` below (a
|
|
162
|
+
// `SimpleSpanProcessor` over an instrumented exporter, or any processor
|
|
163
|
+
// that emits a span of its own), which re-enters this method.
|
|
164
|
+
const outer = this.#counter;
|
|
165
|
+
if (counting)
|
|
166
|
+
this.#counter = createOutcomeCounter();
|
|
167
|
+
let dropped = false;
|
|
168
|
+
let counts;
|
|
169
|
+
try {
|
|
170
|
+
try {
|
|
171
|
+
redactSpan(this.#mask, span);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
this.#warnDropped(error instanceof UnredactableFieldError ? error.message : "unexpected span shape");
|
|
175
|
+
dropped = true;
|
|
176
|
+
}
|
|
177
|
+
// Snapshotted before delegating, because this span's numbers are final
|
|
178
|
+
// here and `#report` runs after a nested `onEnd` may have replaced the
|
|
179
|
+
// field.
|
|
180
|
+
if (counting)
|
|
181
|
+
counts = toValueCounts(this.#counter);
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
this.#counter = outer;
|
|
185
|
+
}
|
|
186
|
+
// Reported whether the span was forwarded or dropped, and after the next
|
|
187
|
+
// processor has had it, so an observer cannot affect what is exported.
|
|
188
|
+
try {
|
|
189
|
+
if (!dropped)
|
|
190
|
+
this.#next.onEnd(span);
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
if (counts !== undefined)
|
|
194
|
+
this.#report(counts, dropped);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
#report(values, dropped) {
|
|
198
|
+
if (this.#onOutcome === undefined || this.#reporting)
|
|
199
|
+
return;
|
|
200
|
+
this.#reporting = true;
|
|
201
|
+
try {
|
|
202
|
+
notify(this.#onOutcome, { host: "otel", unit: "span", values, dropped });
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
this.#reporting = false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
shutdown() {
|
|
209
|
+
return this.#next.shutdown();
|
|
210
|
+
}
|
|
211
|
+
forceFlush() {
|
|
212
|
+
return this.#next.forceFlush ? this.#next.forceFlush() : Promise.resolve();
|
|
213
|
+
}
|
|
214
|
+
#warnDropped(reason) {
|
|
215
|
+
if (this.#warned)
|
|
216
|
+
return;
|
|
217
|
+
this.#warned = true;
|
|
218
|
+
const message = `@redact-secret/adapter-otel: dropped a span that could not be redacted (${reason})`;
|
|
219
|
+
try {
|
|
220
|
+
if (typeof process !== "undefined" && typeof process.emitWarning === "function") {
|
|
221
|
+
process.emitWarning(message, { code: "REDACT_SECRET_SPAN_DROPPED" });
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
console.warn(message);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// A warning is best effort; the span is dropped either way.
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@redact-secret/adapter-otel-trace",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A redacting OpenTelemetry JS SpanProcessor (traces only) over the Redact Secret core.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"CHANGELOG.md",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": "20.x || 22.x || 24.x"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/redact-secret/redact-secret-adapters.git",
|
|
27
|
+
"directory": "packages/adapter-otel-trace"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/redact-secret/redact-secret-adapters/tree/main/packages/adapter-otel-trace#readme",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/redact-secret/redact-secret-adapters/issues"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.build.json",
|
|
35
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@redact-secret/adapter": "^0.1.3"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@opentelemetry/sdk-trace-base": "^2.0.0",
|
|
42
|
+
"@redact-secret/core": "^0.1.0-beta.6"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@opentelemetry/sdk-trace-base": "^2.0.0",
|
|
46
|
+
"@redact-secret/core": "^0.1.0-beta.6",
|
|
47
|
+
"@opentelemetry/otlp-transformer": "^0.222.0"
|
|
48
|
+
}
|
|
49
|
+
}
|