@shivaedev/server 0.9.1 → 0.9.3

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/dist/RedactionPolicy.d.ts +5 -1
  3. package/dist/RedactionPolicy.d.ts.map +1 -1
  4. package/dist/RedactionPolicy.js +3 -0
  5. package/dist/RedactionPolicy.js.map +1 -1
  6. package/dist/hostChanges.js +1 -1
  7. package/dist/hostChanges.js.map +1 -1
  8. package/dist/hostRpcRoutes.d.ts.map +1 -1
  9. package/dist/hostRpcRoutes.js +4 -4
  10. package/dist/hostRpcRoutes.js.map +1 -1
  11. package/dist/redactingDiagnostics.d.ts +2 -1
  12. package/dist/redactingDiagnostics.d.ts.map +1 -1
  13. package/dist/redactingDiagnostics.js +15 -3
  14. package/dist/redactingDiagnostics.js.map +1 -1
  15. package/dist/redactingRpcSerialization.d.ts +1 -1
  16. package/dist/redactingRpcSerialization.d.ts.map +1 -1
  17. package/dist/redactingRpcSerialization.js +3 -3
  18. package/dist/redactingRpcSerialization.js.map +1 -1
  19. package/dist/redactingTracer.d.ts +2 -2
  20. package/dist/redactingTracer.d.ts.map +1 -1
  21. package/dist/redactingTracer.js +17 -3
  22. package/dist/redactingTracer.js.map +1 -1
  23. package/dist/rpcRequestTracing.d.ts +5 -0
  24. package/dist/rpcRequestTracing.d.ts.map +1 -0
  25. package/dist/rpcRequestTracing.js +8 -0
  26. package/dist/rpcRequestTracing.js.map +1 -0
  27. package/dist/safeOtlpSerialization.d.ts.map +1 -1
  28. package/dist/safeOtlpSerialization.js +20 -15
  29. package/dist/safeOtlpSerialization.js.map +1 -1
  30. package/dist/schemaSensitiveAst.d.ts +7 -0
  31. package/dist/schemaSensitiveAst.d.ts.map +1 -0
  32. package/dist/schemaSensitiveAst.js +102 -0
  33. package/dist/schemaSensitiveAst.js.map +1 -0
  34. package/dist/schemaSensitiveKeys.d.ts +1 -0
  35. package/dist/schemaSensitiveKeys.d.ts.map +1 -1
  36. package/dist/schemaSensitiveKeys.js +27 -123
  37. package/dist/schemaSensitiveKeys.js.map +1 -1
  38. package/package.json +11 -9
  39. package/src/RedactionPolicy.ts +7 -1
  40. package/src/hostChanges.ts +4 -2
  41. package/src/hostRpcRoutes.ts +4 -4
  42. package/src/redactingDiagnostics.ts +15 -3
  43. package/src/redactingRpcSerialization.ts +3 -2
  44. package/src/redactingTracer.ts +18 -4
  45. package/src/rpcRequestTracing.ts +12 -0
  46. package/src/safeOtlpSerialization.ts +20 -15
  47. package/src/schemaSensitiveAst.ts +106 -0
  48. package/src/schemaSensitiveKeys.ts +28 -127
@@ -2,7 +2,7 @@ import { Effect, Layer } from "effect";
2
2
  import type { OtlpResource, OtlpTracer } from "effect/observability";
3
3
  import { OtlpSerialization } from "effect/observability";
4
4
  import { isSensitiveKey, REDACTED, redactText, type SensitiveKey } from "@shivaedev/errors/sensitive.ts";
5
- import { RedactionPolicy } from "#RedactionPolicy.ts";
5
+ import { type HostRedactionPolicy, RedactionPolicy, redactionForRpc } from "#RedactionPolicy.ts";
6
6
  import { redactOtlpUrl } from "#redactOtlpUrl.ts";
7
7
 
8
8
  function otlpSensitive(sensitive: SensitiveKey): SensitiveKey {
@@ -37,25 +37,30 @@ function spanName(span: { readonly name: string; readonly kind: number; readonly
37
37
  return span.name;
38
38
  }
39
39
 
40
- function redactTraces(data: OtlpTracer.TraceData, sensitive: SensitiveKey): OtlpTracer.TraceData {
41
- function text(value: string) {
42
- return redactOtlpText(value, "", sensitive);
43
- }
40
+ function redactTraces(data: OtlpTracer.TraceData, policy: HostRedactionPolicy): OtlpTracer.TraceData {
41
+ const resourceSensitive = otlpSensitive(policy);
44
42
  return {
45
43
  ...data,
46
44
  resourceSpans: data.resourceSpans.map((resource) => ({
47
45
  ...resource,
48
- resource: { ...resource.resource, attributes: attributes(resource.resource.attributes, sensitive) },
46
+ resource: { ...resource.resource, attributes: attributes(resource.resource.attributes, resourceSensitive) },
49
47
  scopeSpans: resource.scopeSpans.map((scope) => ({
50
48
  ...scope,
51
- spans: scope.spans.map((span) => ({
52
- ...span,
53
- attributes: attributes(span.attributes, sensitive),
54
- events: span.events.map((event) => ({ ...event, attributes: attributes(event.attributes, sensitive), name: text(event.name) })),
55
- links: span.links.map((link) => ({ ...link, attributes: attributes(link.attributes, sensitive) })),
56
- name: text(spanName(span)),
57
- status: { ...span.status, ...(span.status.message === undefined ? {} : { message: text(span.status.message) }) },
58
- })),
49
+ spans: scope.spans.map((span) => {
50
+ const method = span.attributes.find(({ key }) => key === "rpc.method")?.value.stringValue;
51
+ const sensitive = otlpSensitive(redactionForRpc(policy, method));
52
+ function text(value: string) {
53
+ return redactOtlpText(value, "", sensitive);
54
+ }
55
+ return {
56
+ ...span,
57
+ attributes: attributes(span.attributes, sensitive),
58
+ events: span.events.map((event) => ({ ...event, attributes: attributes(event.attributes, sensitive), name: text(event.name) })),
59
+ links: span.links.map((link) => ({ ...link, attributes: attributes(link.attributes, sensitive) })),
60
+ name: text(spanName(span)),
61
+ status: { ...span.status, ...(span.status.message === undefined ? {} : { message: text(span.status.message) }) },
62
+ };
63
+ }),
59
64
  })),
60
65
  })),
61
66
  };
@@ -64,7 +69,7 @@ export const safeOtlpSerialization = Layer.effect(
64
69
  OtlpSerialization.OtlpSerialization,
65
70
  Effect.gen(function* () {
66
71
  const serialization = yield* OtlpSerialization.OtlpSerialization;
67
- const sensitive = otlpSensitive(yield* RedactionPolicy);
72
+ const sensitive = yield* RedactionPolicy;
68
73
  return OtlpSerialization.OtlpSerialization.of({
69
74
  ...serialization,
70
75
  traces: (data) => serialization.traces(redactTraces(data, sensitive)),
@@ -0,0 +1,106 @@
1
+ import { Predicate, type SchemaAST } from "effect";
2
+ import { isSensitiveKey } from "@shivaedev/errors/sensitive.ts";
3
+
4
+ function children(ast: SchemaAST.AST): readonly SchemaAST.AST[] {
5
+ switch (ast._tag) {
6
+ case "Objects":
7
+ return [...ast.propertySignatures.map((property) => property.type), ...ast.indexSignatures.flatMap((index) => [index.parameter, index.type])];
8
+ case "Arrays":
9
+ return [...ast.elements, ...ast.rest];
10
+ case "Union":
11
+ return ast.types;
12
+ case "Declaration":
13
+ return ast.typeParameters;
14
+ case "Suspend":
15
+ return [ast.thunk()];
16
+ default:
17
+ return [];
18
+ }
19
+ }
20
+ function sensitiveCheck(check: SchemaAST.Check<never>): boolean {
21
+ return check.annotations?.sensitive === true || (check._tag === "FilterGroup" && check.checks.some(sensitiveCheck));
22
+ }
23
+ export function marked(ast: SchemaAST.AST, seen = new Set<SchemaAST.AST>()): boolean {
24
+ if (seen.has(ast)) {
25
+ return false;
26
+ }
27
+ seen.add(ast);
28
+ if (
29
+ ast.annotations?.sensitive === true
30
+ || ast.context?.annotations?.sensitive === true
31
+ || ast.checks?.some(sensitiveCheck)
32
+ || (Predicate.hasProperty(ast.annotations?.representation, "id") && ast.annotations.representation.id === "effect/schema/Redacted")
33
+ ) {
34
+ return true;
35
+ }
36
+ const nested = ast._tag === "Objects" ? ast.indexSignatures.map((index) => index.type) : children(ast);
37
+ return (ast.encoding?.some((link) => marked(link.to, seen)) ?? false) || nested.some((type) => marked(type, seen));
38
+ }
39
+ function containsSensitive(ast: SchemaAST.AST, keys: ReadonlySet<string>): boolean {
40
+ return (
41
+ marked(ast)
42
+ || propertiesOf(ast).some((property) => typeof property.name === "string" && (keys.has(property.name) || isSensitiveKey(property.name)))
43
+ );
44
+ }
45
+ export function collectEncodedKeys(ast: SchemaAST.AST, keys: Set<string>) {
46
+ let source = ast;
47
+ let sensitive = false;
48
+ for (const link of ast.encoding ?? []) {
49
+ sensitive ||= containsSensitive(source, keys);
50
+ if (sensitive) {
51
+ for (const property of propertiesOf(link.to)) {
52
+ if (typeof property.name === "string") {
53
+ keys.add(property.name);
54
+ }
55
+ }
56
+ }
57
+ source = link.to;
58
+ }
59
+ }
60
+ function fixedOutput(ast: SchemaAST.AST): boolean {
61
+ if (ast._tag === "Union") {
62
+ return ast.types.every(fixedOutput);
63
+ }
64
+ return ast._tag === "Objects" && ast.indexSignatures.length === 0;
65
+ }
66
+ export function maskedValue(ast: SchemaAST.AST, keys: ReadonlySet<string>, seen = new Set<SchemaAST.AST>()): boolean {
67
+ if (seen.has(ast)) {
68
+ return false;
69
+ }
70
+ seen.add(ast);
71
+ if (marked(ast)) {
72
+ return true;
73
+ }
74
+ let source = ast;
75
+ let sensitive = false;
76
+ for (const link of ast.encoding ?? []) {
77
+ sensitive ||= containsSensitive(source, keys);
78
+ if (sensitive && !fixedOutput(link.to)) {
79
+ return true;
80
+ }
81
+ source = link.to;
82
+ }
83
+ const nested = ast._tag === "Objects" ? ast.indexSignatures.map((index) => index.type) : children(ast);
84
+ return nested.some((type) => maskedValue(type, keys, seen));
85
+ }
86
+ export function collect(ast: SchemaAST.AST, nodes: Set<SchemaAST.AST>, encodings = true) {
87
+ if (nodes.has(ast)) {
88
+ return;
89
+ }
90
+ nodes.add(ast);
91
+ for (const child of [...children(ast), ...(encodings ? (ast.encoding?.map((link) => link.to) ?? []) : [])]) {
92
+ collect(child, nodes, encodings);
93
+ }
94
+ }
95
+ function propertiesOf(ast: SchemaAST.AST) {
96
+ const nodes = new Set<SchemaAST.AST>();
97
+ collect(ast, nodes, false);
98
+ return [...nodes].flatMap((node) => (node._tag === "Objects" ? node.propertySignatures : []));
99
+ }
100
+ export function collectFieldKeys(properties: SchemaAST.Objects["propertySignatures"], keys: Set<string>, sensitive: (ast: SchemaAST.AST) => boolean) {
101
+ for (const property of properties) {
102
+ if (typeof property.name === "string" && sensitive(property.type)) {
103
+ keys.add(property.name);
104
+ }
105
+ }
106
+ }
@@ -1,130 +1,20 @@
1
- import { Predicate, type Schema, type SchemaAST } from "effect";
1
+ import type { Schema, SchemaAST } from "effect";
2
2
  import { Rpc, type RpcGroup } from "effect/rpc";
3
3
  import { isSensitiveKey } from "@shivaedev/errors/sensitive.ts";
4
+ import { collect, collectEncodedKeys, collectFieldKeys, marked, maskedValue } from "#schemaSensitiveAst.ts";
4
5
 
5
- function children(ast: SchemaAST.AST): readonly SchemaAST.AST[] {
6
- switch (ast._tag) {
7
- case "Objects":
8
- return [...ast.propertySignatures.map((property) => property.type), ...ast.indexSignatures.flatMap((index) => [index.parameter, index.type])];
9
- case "Arrays":
10
- return [...ast.elements, ...ast.rest];
11
- case "Union":
12
- return ast.types;
13
- case "Declaration":
14
- return ast.typeParameters;
15
- case "Suspend":
16
- return [ast.thunk()];
17
- default:
18
- return [];
19
- }
20
- }
21
- function sensitiveCheck(check: SchemaAST.Check<never>): boolean {
22
- return check.annotations?.sensitive === true || (check._tag === "FilterGroup" && check.checks.some(sensitiveCheck));
23
- }
24
- function marked(ast: SchemaAST.AST, seen = new Set<SchemaAST.AST>()): boolean {
25
- if (seen.has(ast)) {
26
- return false;
27
- }
28
- seen.add(ast);
29
- if (
30
- ast.annotations?.sensitive === true
31
- || ast.context?.annotations?.sensitive === true
32
- || ast.checks?.some(sensitiveCheck)
33
- || (Predicate.hasProperty(ast.annotations?.representation, "id") && ast.annotations.representation.id === "effect/schema/Redacted")
34
- ) {
35
- return true;
36
- }
37
- const nested = ast._tag === "Objects" ? ast.indexSignatures.map((index) => index.type) : children(ast);
38
- return (ast.encoding?.some((link) => marked(link.to, seen)) ?? false) || nested.some((type) => marked(type, seen));
39
- }
40
- function containsSensitive(ast: SchemaAST.AST, keys: ReadonlySet<string>): boolean {
41
- return (
42
- marked(ast)
43
- || propertiesOf(ast).some((property) => typeof property.name === "string" && (keys.has(property.name) || isSensitiveKey(property.name)))
44
- );
45
- }
46
- function collectEncodedKeys(ast: SchemaAST.AST, keys: Set<string>) {
47
- let source = ast;
48
- let sensitive = false;
49
- for (const link of ast.encoding ?? []) {
50
- sensitive ||= containsSensitive(source, keys);
51
- if (sensitive) {
52
- for (const property of propertiesOf(link.to)) {
53
- if (typeof property.name === "string") {
54
- keys.add(property.name);
55
- }
56
- }
57
- }
58
- source = link.to;
59
- }
60
- }
61
- function fixedOutput(ast: SchemaAST.AST): boolean {
62
- if (ast._tag === "Union") {
63
- return ast.types.every(fixedOutput);
64
- }
65
- return ast._tag === "Objects" && ast.indexSignatures.length === 0;
66
- }
67
- function maskedValue(ast: SchemaAST.AST, keys: ReadonlySet<string>, seen = new Set<SchemaAST.AST>()): boolean {
68
- if (seen.has(ast)) {
69
- return false;
70
- }
71
- seen.add(ast);
72
- if (marked(ast)) {
73
- return true;
74
- }
75
- let source = ast;
76
- let sensitive = false;
77
- for (const link of ast.encoding ?? []) {
78
- sensitive ||= containsSensitive(source, keys);
79
- if (sensitive && !fixedOutput(link.to)) {
80
- return true;
81
- }
82
- source = link.to;
83
- }
84
- const nested = ast._tag === "Objects" ? ast.indexSignatures.map((index) => index.type) : children(ast);
85
- return nested.some((type) => maskedValue(type, keys, seen));
86
- }
87
- function collect(ast: SchemaAST.AST, nodes: Set<SchemaAST.AST>, encodings = true) {
88
- if (nodes.has(ast)) {
89
- return;
90
- }
91
- nodes.add(ast);
92
- for (const child of [...children(ast), ...(encodings ? (ast.encoding?.map((link) => link.to) ?? []) : [])]) {
93
- collect(child, nodes, encodings);
94
- }
95
- }
96
- function propertiesOf(ast: SchemaAST.AST) {
97
- const nodes = new Set<SchemaAST.AST>();
98
- collect(ast, nodes, false);
99
- return [...nodes].flatMap((node) => (node._tag === "Objects" ? node.propertySignatures : []));
100
- }
101
- function collectFieldKeys(properties: SchemaAST.Objects["propertySignatures"], keys: Set<string>, sensitive: (ast: SchemaAST.AST) => boolean) {
102
- for (const property of properties) {
103
- if (typeof property.name === "string" && sensitive(property.type)) {
104
- keys.add(property.name);
105
- }
106
- }
107
- }
108
- export function schemaSensitiveKeys<R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>, extra: readonly string[] = []) {
6
+ function rpcSensitiveKeys(rpc: Rpc.AnyWithProps, extra: readonly string[]) {
109
7
  const keys = new Set(extra);
110
8
  const nodes = new Set<SchemaAST.AST>();
111
- const requests: Rpc.AnyWithProps[] = [];
112
- for (const rpc of group.requests.values()) {
113
- if (Rpc.isRpc(rpc)) {
114
- requests.push(rpc);
115
- }
116
- }
117
- for (const rpc of requests) {
118
- const schemas: Schema.Top[] = [
119
- rpc.payloadSchema,
120
- rpc.successSchema,
121
- rpc.errorSchema,
122
- rpc.defectSchema,
123
- ...[...rpc.middlewares].map((m) => m.error),
124
- ];
125
- for (const schema of schemas) {
126
- collect(schema.ast, nodes);
127
- }
9
+ const schemas: Schema.Top[] = [
10
+ rpc.payloadSchema,
11
+ rpc.successSchema,
12
+ rpc.errorSchema,
13
+ rpc.defectSchema,
14
+ ...[...rpc.middlewares].map((m) => m.error),
15
+ ];
16
+ for (const schema of schemas) {
17
+ collect(schema.ast, nodes);
128
18
  }
129
19
  const properties = [...nodes].flatMap((ast) => (ast._tag === "Objects" ? ast.propertySignatures : []));
130
20
  collectFieldKeys(properties, keys, marked);
@@ -136,13 +26,24 @@ export function schemaSensitiveKeys<R extends Rpc.Any>(group: RpcGroup.RpcGroup<
136
26
  collectEncodedKeys(ast, keys);
137
27
  }
138
28
  } while (keys.size !== previousSize);
139
- const roots = requests.map((rpc) => ({
29
+ return Object.assign((key: string) => keys.has(key) || isSensitiveKey(key), {
140
30
  die: maskedValue(rpc.defectSchema.ast, keys),
141
31
  fail: [rpc.errorSchema, ...[...rpc.middlewares].map((m) => m.error)].some((schema) => maskedValue(schema.ast, keys)),
142
- tag: rpc._tag,
143
- }));
144
- return Object.assign((key: string) => keys.has(key) || isSensitiveKey(key), {
32
+ });
33
+ }
34
+ export function schemaSensitiveKeys<R extends Rpc.Any>(group: RpcGroup.RpcGroup<R>, extra: readonly string[] = []) {
35
+ const policies = new Map<string, ReturnType<typeof rpcSensitiveKeys>>();
36
+ for (const rpc of group.requests.values()) {
37
+ if (Rpc.isRpc(rpc)) {
38
+ policies.set(rpc._tag, rpcSensitiveKeys(rpc, extra));
39
+ }
40
+ }
41
+ function sensitive(key: string) {
42
+ return extra.includes(key) || isSensitiveKey(key) || [...policies.values()].some((policy) => policy(key));
43
+ }
44
+ return Object.assign(sensitive, {
45
+ forRpc: (tag: string | undefined) => (tag === undefined ? sensitive : (policies.get(tag) ?? sensitive)),
145
46
  maskFailure: (tag: string | undefined, reason: "Fail" | "Die") =>
146
- roots.some((root) => (tag === undefined || tag === root.tag) && (reason === "Fail" ? root.fail : root.die)),
47
+ [...policies].some(([method, policy]) => (tag === undefined || tag === method) && (reason === "Fail" ? policy.fail : policy.die)),
147
48
  });
148
49
  }