@g8r-security/agent-shield-sdk 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,11 +11,12 @@ This is a **TypeScript source package** — no compile step. Consumers reference
11
11
  ## Quick Start
12
12
 
13
13
  ```typescript
14
- import { AgentShield } from '@g8r-security/agent-shield-sdk';
14
+ import { AgentShield, tenantId } from '@g8r-security/agent-shield-sdk';
15
15
 
16
16
  const shield = new AgentShield({
17
17
  consoleUrl: 'https://shield.yourcompany.com',
18
18
  apiKey: 'sk-shield-...',
19
+ tenantId: tenantId('acme-corp'),
19
20
  agentId: 'enterprise-assistant',
20
21
  department: 'Finance',
21
22
  userId: 'usr_FIN_042',
@@ -75,27 +76,32 @@ import { redactSensitiveData } from '@g8r-security/agent-shield-sdk';
75
76
  const { redacted, tokensReplaced } = redactSensitiveData(input);
76
77
  ```
77
78
 
78
- ## VPC Redaction Layer
79
+ ## Redaction Layer
79
80
 
80
- Sensitive data is detected and replaced **locally** before leaving the VPC. The gateway never sees raw secrets.
81
+ Sensitive data is detected and replaced **locally** before the prompt leaves the process on both the policy-check and audit-log paths, so the gateway never receives recognized raw secrets.
82
+
83
+ > ⚠️ **Best-effort, not exhaustive.** Redaction is pattern- and entropy-based. It catches the formats listed below, but it **cannot** catch every secret or PII shape — unstructured PII (names, addresses), free-form secrets below the entropy threshold, or novel token formats may pass through. Treat this as one layer of defense-in-depth, not a compliance guarantee, and keep downstream controls and human review in place.
81
84
 
82
85
  ### Detection Patterns
83
86
 
84
- | Pattern | Label | Maps to |
85
- |---|---|---|
86
- | BIP-32 extended keys (`xpub`, `xprv`, `ypub`, …) | `[REDACTED:BIP32_KEY]` | PCI-DSS 3.4 |
87
- | WIF private keys (Base58, starts with `5`, `K`, or `L`) | `[REDACTED:WIF_KEY]` | PCI-DSS 3.4 |
88
- | 256-bit hex keys (64 hex chars, optional `0x` prefix) | `[REDACTED:HEX_KEY]` | PCI-DSS 3.4, GDPR Art. 32 |
89
- | PEM private / public key blocks | `[REDACTED:PEM_KEY]` | GDPR Art. 32 |
90
- | `custodial-id:…` | `[REDACTED:CUSTODIAL_ID]` | GDPR Art. 32 |
91
- | `cust-{digits}` | `[REDACTED:CUST_ID]` | GDPR Art. 32 |
92
- | `wallet-id:…` | `[REDACTED:WALLET_ID]` | GDPR Art. 32 |
93
- | `vault-id:…` | `[REDACTED:VAULT_ID]` | GDPR Art. 32 |
94
- | High Shannon entropy strings (≥4.5 bits/char, ≥32 chars) | `[REDACTED:HIGH_ENTROPY]` | GDPR Art. 32, PCI-DSS 3.4 |
87
+ | Pattern | Label |
88
+ |---|---|
89
+ | BIP-32 extended keys (`xpub`, `xprv`, `ypub`, …) | `[REDACTED:BIP32_KEY]` |
90
+ | WIF private keys (Base58, starts with `5`, `K`, or `L`) | `[REDACTED:WIF_KEY]` |
91
+ | 256-bit hex keys (64 hex chars, optional `0x` prefix) | `[REDACTED:HEX_KEY]` |
92
+ | PEM private / public key blocks | `[REDACTED:PEM_KEY]` |
93
+ | `custodial-id:…` / `cust-{digits}` / `wallet-id:…` / `vault-id:…` | `[REDACTED:CUSTODIAL_ID]` etc. |
94
+ | Card numbers (13–19 digits, Luhn-validated) | `[REDACTED:CARD]` |
95
+ | US SSNs (`123-45-6789`) | `[REDACTED:SSN]` |
96
+ | Email addresses | `[REDACTED:EMAIL]` |
97
+ | Phone numbers (separated, e.g. `415-555-0199`) | `[REDACTED:PHONE]` |
98
+ | High Shannon entropy strings (≥4.5 bits/char, ≥32 chars) | `[REDACTED:HIGH_ENTROPY]` |
99
+
100
+ These map to controls such as **GDPR Art. 32** (security of processing) and **PCI-DSS** PAN handling by reducing sensitive-data exposure — they *support* those controls rather than satisfy them on their own.
95
101
 
96
102
  ### Shannon Entropy Detection
97
103
 
98
- Any token that is 32+ characters with Shannon entropy ≥ 4.5 bits/char is caught as a generic high-entropy secret. This acts as a catch-all for API keys, tokens, and other secrets that don't match a known format.
104
+ Any token that is 32+ characters with Shannon entropy ≥ 4.5 bits/char is caught as a generic high-entropy secret. This is a best-effort catch for many API keys and tokens that don't match a known format — but secrets shorter than 32 chars or below the entropy threshold will not be caught.
99
105
 
100
106
  ```
101
107
  H = -Σ p(c) × log₂(p(c)) for each unique character c
package/dist/index.d.mts CHANGED
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * Branded id types + constructors used by the SDK.
3
3
  *
4
- * Vendored from the engine's `types.ts` so the published
5
- * `@g8r-security/agent-shield-sdk` package carries no `@g8r-security/core` dependency the
6
- * engine is private IP and must not be a public-package dependency. Only
7
- * the id types/helpers the SDK actually uses are copied here.
4
+ * Self-contained in the SDK so the published `@g8r-security/agent-shield-sdk`
5
+ * package has no dependency on internal packages. Only the id types/helpers the
6
+ * SDK actually uses are included here.
8
7
  */
9
8
  /**
10
9
  * Identifies a single tenant in the multi-tenant governance plane.
@@ -17,21 +16,68 @@ type TenantId = string & {
17
16
  type RequestId = string & {
18
17
  readonly __brand: 'RequestId';
19
18
  };
19
+ /**
20
+ * Cast a string to TenantId. Use at boundaries (config, env, request body).
21
+ * Enforces the charset / length contract: 1-64 chars of `[a-z0-9-]`,
22
+ * starting with `[a-z0-9]` — catches programmatic misuse (config typos,
23
+ * hard-coded slugs that drift).
24
+ */
25
+ declare function tenantId(s: string): TenantId;
26
+ /** Generate a fresh request ID. Prefers crypto.randomUUID where available. */
27
+ declare function newRequestId(): RequestId;
28
+
29
+ /**
30
+ * Local-First Sensitive Data Redaction
31
+ *
32
+ * Best-effort redaction of common secret and PII formats BEFORE prompts reach
33
+ * the policy gateway. Covers cryptographic key formats, custodial identifiers,
34
+ * high-entropy strings, and common PII (card numbers validated via Luhn, US
35
+ * SSNs, email addresses, and phone numbers).
36
+ *
37
+ * IMPORTANT — this is a defense-in-depth layer, NOT a guarantee of completeness.
38
+ * Pattern- and entropy-based redaction cannot catch every secret or PII shape
39
+ * (e.g. unstructured PII, names, novel token formats, or values below the
40
+ * entropy threshold). Do not rely on it as a sole control; keep downstream
41
+ * safeguards and human review in place.
42
+ *
43
+ * It helps *support* (does not by itself satisfy) controls such as GDPR Art. 32
44
+ * and PCI-DSS PAN-handling by reducing sensitive-data exposure to the gateway.
45
+ */
46
+ interface RedactionResult {
47
+ /** The input with all sensitive tokens replaced by placeholder strings. */
48
+ redacted: string;
49
+ /** The original sensitive token strings that were replaced. */
50
+ tokensReplaced: string[];
51
+ }
52
+ /**
53
+ * Redact sensitive data from a prompt string before it reaches the gateway.
54
+ *
55
+ * Processing order (important — PEM first to avoid splitting on inner patterns):
56
+ * 1. PEM private/public key blocks
57
+ * 2. BIP-32 extended keys
58
+ * 3. WIF private keys
59
+ * 4. Raw hex 256-bit keys
60
+ * 5. Custodial IDs (all four variants)
61
+ * 6. PII — card numbers (Luhn-validated), SSNs, emails, phone numbers
62
+ * 7. High-entropy string catch-all
63
+ */
64
+ declare function redactSensitiveData(input: string): RedactionResult;
20
65
 
21
66
  /**
22
67
  * G8R Agent Shield SDK
23
68
  *
24
69
  * Lightweight TypeScript client that wraps LLM calls with policy enforcement.
25
- * Automatically intercepts prompts, applies local-first VPC redaction (BitGo),
70
+ * Automatically intercepts prompts, applies best-effort local-first redaction,
26
71
  * checks them against the G8R policy engine, and logs all activity to the
27
72
  * Agent Shield Console.
28
73
  *
29
74
  * Usage:
30
- * import { AgentShield } from '@g8r-security/agent-shield-sdk';
75
+ * import { AgentShield, tenantId } from '@g8r-security/agent-shield-sdk';
31
76
  *
32
77
  * const shield = new AgentShield({
33
78
  * consoleUrl: 'https://shield.yourcompany.com',
34
79
  * apiKey: 'sk-shield-...',
80
+ * tenantId: tenantId('acme-corp'),
35
81
  * department: 'Finance',
36
82
  * userId: 'usr_FIN_042',
37
83
  * aiModel: 'GPT-4o',
@@ -84,7 +130,7 @@ interface PolicyCheckResult {
84
130
  /**
85
131
  * Tokens that were redacted from the prompt before it reached the gateway.
86
132
  * Undefined when no tokens were redacted (clean prompt).
87
- * Populated by the BitGo VPC local-first redaction layer.
133
+ * Populated by the local-first redaction layer.
88
134
  */
89
135
  redactedTokens?: string[];
90
136
  }
@@ -99,9 +145,10 @@ declare class AgentShield {
99
145
  /**
100
146
  * Check a prompt against the policy engine before sending to the LLM.
101
147
  *
102
- * Applies local-first VPC redaction (BitGo) before sending to the gateway,
103
- * ensuring signing keys, custodial IDs, and high-entropy secrets never leave
104
- * the local process in plaintext.
148
+ * Applies best-effort local-first redaction before sending to the gateway, so
149
+ * recognized signing keys, custodial IDs, common PII, and high-entropy secrets
150
+ * are stripped before the prompt leaves the process. Redaction is one layer of
151
+ * defense, not a guarantee that every secret is caught (see redaction.ts).
105
152
  *
106
153
  * Returns the policy decision without executing the LLM call.
107
154
  */
@@ -130,6 +177,9 @@ declare class AgentShield {
130
177
  wrap<T>(llmCallFactory: () => Promise<T>, prompt: string): Promise<T>;
131
178
  /**
132
179
  * Log an interaction to the Agent Shield Console.
180
+ *
181
+ * Redacts the prompt before transmitting: the audit trail must not store or
182
+ * carry raw secrets/PII, and this is an egress point just like /check.
133
183
  */
134
184
  private log;
135
185
  }
@@ -143,4 +193,4 @@ declare class ShieldBlockedError extends Error {
143
193
  constructor(reason: string, violatedRule: string | null, complianceMappings: PolicyCheckResult['complianceMappings'], sessionRevoked?: boolean);
144
194
  }
145
195
 
146
- export { AgentShield, type PolicyCheckResult, ShieldBlockedError, type ShieldConfig, type ShieldLogEntry };
196
+ export { AgentShield, type PolicyCheckResult, type RedactionResult, type RequestId, ShieldBlockedError, type ShieldConfig, type ShieldLogEntry, type TenantId, newRequestId, redactSensitiveData, tenantId };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * Branded id types + constructors used by the SDK.
3
3
  *
4
- * Vendored from the engine's `types.ts` so the published
5
- * `@g8r-security/agent-shield-sdk` package carries no `@g8r-security/core` dependency the
6
- * engine is private IP and must not be a public-package dependency. Only
7
- * the id types/helpers the SDK actually uses are copied here.
4
+ * Self-contained in the SDK so the published `@g8r-security/agent-shield-sdk`
5
+ * package has no dependency on internal packages. Only the id types/helpers the
6
+ * SDK actually uses are included here.
8
7
  */
9
8
  /**
10
9
  * Identifies a single tenant in the multi-tenant governance plane.
@@ -17,21 +16,68 @@ type TenantId = string & {
17
16
  type RequestId = string & {
18
17
  readonly __brand: 'RequestId';
19
18
  };
19
+ /**
20
+ * Cast a string to TenantId. Use at boundaries (config, env, request body).
21
+ * Enforces the charset / length contract: 1-64 chars of `[a-z0-9-]`,
22
+ * starting with `[a-z0-9]` — catches programmatic misuse (config typos,
23
+ * hard-coded slugs that drift).
24
+ */
25
+ declare function tenantId(s: string): TenantId;
26
+ /** Generate a fresh request ID. Prefers crypto.randomUUID where available. */
27
+ declare function newRequestId(): RequestId;
28
+
29
+ /**
30
+ * Local-First Sensitive Data Redaction
31
+ *
32
+ * Best-effort redaction of common secret and PII formats BEFORE prompts reach
33
+ * the policy gateway. Covers cryptographic key formats, custodial identifiers,
34
+ * high-entropy strings, and common PII (card numbers validated via Luhn, US
35
+ * SSNs, email addresses, and phone numbers).
36
+ *
37
+ * IMPORTANT — this is a defense-in-depth layer, NOT a guarantee of completeness.
38
+ * Pattern- and entropy-based redaction cannot catch every secret or PII shape
39
+ * (e.g. unstructured PII, names, novel token formats, or values below the
40
+ * entropy threshold). Do not rely on it as a sole control; keep downstream
41
+ * safeguards and human review in place.
42
+ *
43
+ * It helps *support* (does not by itself satisfy) controls such as GDPR Art. 32
44
+ * and PCI-DSS PAN-handling by reducing sensitive-data exposure to the gateway.
45
+ */
46
+ interface RedactionResult {
47
+ /** The input with all sensitive tokens replaced by placeholder strings. */
48
+ redacted: string;
49
+ /** The original sensitive token strings that were replaced. */
50
+ tokensReplaced: string[];
51
+ }
52
+ /**
53
+ * Redact sensitive data from a prompt string before it reaches the gateway.
54
+ *
55
+ * Processing order (important — PEM first to avoid splitting on inner patterns):
56
+ * 1. PEM private/public key blocks
57
+ * 2. BIP-32 extended keys
58
+ * 3. WIF private keys
59
+ * 4. Raw hex 256-bit keys
60
+ * 5. Custodial IDs (all four variants)
61
+ * 6. PII — card numbers (Luhn-validated), SSNs, emails, phone numbers
62
+ * 7. High-entropy string catch-all
63
+ */
64
+ declare function redactSensitiveData(input: string): RedactionResult;
20
65
 
21
66
  /**
22
67
  * G8R Agent Shield SDK
23
68
  *
24
69
  * Lightweight TypeScript client that wraps LLM calls with policy enforcement.
25
- * Automatically intercepts prompts, applies local-first VPC redaction (BitGo),
70
+ * Automatically intercepts prompts, applies best-effort local-first redaction,
26
71
  * checks them against the G8R policy engine, and logs all activity to the
27
72
  * Agent Shield Console.
28
73
  *
29
74
  * Usage:
30
- * import { AgentShield } from '@g8r-security/agent-shield-sdk';
75
+ * import { AgentShield, tenantId } from '@g8r-security/agent-shield-sdk';
31
76
  *
32
77
  * const shield = new AgentShield({
33
78
  * consoleUrl: 'https://shield.yourcompany.com',
34
79
  * apiKey: 'sk-shield-...',
80
+ * tenantId: tenantId('acme-corp'),
35
81
  * department: 'Finance',
36
82
  * userId: 'usr_FIN_042',
37
83
  * aiModel: 'GPT-4o',
@@ -84,7 +130,7 @@ interface PolicyCheckResult {
84
130
  /**
85
131
  * Tokens that were redacted from the prompt before it reached the gateway.
86
132
  * Undefined when no tokens were redacted (clean prompt).
87
- * Populated by the BitGo VPC local-first redaction layer.
133
+ * Populated by the local-first redaction layer.
88
134
  */
89
135
  redactedTokens?: string[];
90
136
  }
@@ -99,9 +145,10 @@ declare class AgentShield {
99
145
  /**
100
146
  * Check a prompt against the policy engine before sending to the LLM.
101
147
  *
102
- * Applies local-first VPC redaction (BitGo) before sending to the gateway,
103
- * ensuring signing keys, custodial IDs, and high-entropy secrets never leave
104
- * the local process in plaintext.
148
+ * Applies best-effort local-first redaction before sending to the gateway, so
149
+ * recognized signing keys, custodial IDs, common PII, and high-entropy secrets
150
+ * are stripped before the prompt leaves the process. Redaction is one layer of
151
+ * defense, not a guarantee that every secret is caught (see redaction.ts).
105
152
  *
106
153
  * Returns the policy decision without executing the LLM call.
107
154
  */
@@ -130,6 +177,9 @@ declare class AgentShield {
130
177
  wrap<T>(llmCallFactory: () => Promise<T>, prompt: string): Promise<T>;
131
178
  /**
132
179
  * Log an interaction to the Agent Shield Console.
180
+ *
181
+ * Redacts the prompt before transmitting: the audit trail must not store or
182
+ * carry raw secrets/PII, and this is an egress point just like /check.
133
183
  */
134
184
  private log;
135
185
  }
@@ -143,4 +193,4 @@ declare class ShieldBlockedError extends Error {
143
193
  constructor(reason: string, violatedRule: string | null, complianceMappings: PolicyCheckResult['complianceMappings'], sessionRevoked?: boolean);
144
194
  }
145
195
 
146
- export { AgentShield, type PolicyCheckResult, ShieldBlockedError, type ShieldConfig, type ShieldLogEntry };
196
+ export { AgentShield, type PolicyCheckResult, type RedactionResult, type RequestId, ShieldBlockedError, type ShieldConfig, type ShieldLogEntry, type TenantId, newRequestId, redactSensitiveData, tenantId };
package/dist/index.js CHANGED
@@ -21,12 +21,25 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AgentShield: () => AgentShield,
24
- ShieldBlockedError: () => ShieldBlockedError
24
+ ShieldBlockedError: () => ShieldBlockedError,
25
+ newRequestId: () => newRequestId,
26
+ redactSensitiveData: () => redactSensitiveData,
27
+ tenantId: () => tenantId
25
28
  });
26
29
  module.exports = __toCommonJS(index_exports);
27
30
  var import_ts_pattern = require("ts-pattern");
28
31
 
29
32
  // src/ids.ts
33
+ var TENANT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
34
+ function tenantId(s) {
35
+ if (!s) throw new Error("tenantId cannot be empty");
36
+ if (!TENANT_ID_PATTERN.test(s)) {
37
+ throw new Error(
38
+ `tenantId must be 1-64 chars, [a-z0-9-], starting with [a-z0-9] (got ${JSON.stringify(s)})`
39
+ );
40
+ }
41
+ return s;
42
+ }
30
43
  function newRequestId() {
31
44
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
32
45
  return crypto.randomUUID();
@@ -88,6 +101,24 @@ var CUSTODIAL_ID_PATTERN = /\bcustodial-id:[A-Za-z0-9_-]+\b/g;
88
101
  var CUST_PATTERN = /\bcust-\d+\b/gi;
89
102
  var WALLET_ID_PATTERN = /\bwallet-id:[A-Za-z0-9_-]+\b/g;
90
103
  var VAULT_ID_PATTERN = /\bvault-id:[A-Za-z0-9_-]+\b/g;
104
+ var EMAIL_PATTERN = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
105
+ var SSN_PATTERN = /\b\d{3}[ -]\d{2}[ -]\d{4}\b/g;
106
+ var PHONE_PATTERN = /\b(?:\+?\d{1,3}[ .-]?)?\(?\d{3}\)?[ .-]\d{3}[ .-]\d{4}\b/g;
107
+ var CARD_CANDIDATE_PATTERN = /\b\d(?:[ -]?\d){12,18}\b/g;
108
+ function luhnValid(digits) {
109
+ let sum = 0;
110
+ let double = false;
111
+ for (let i = digits.length - 1; i >= 0; i--) {
112
+ let d = digits.charCodeAt(i) - 48;
113
+ if (double) {
114
+ d *= 2;
115
+ if (d > 9) d -= 9;
116
+ }
117
+ sum += d;
118
+ double = !double;
119
+ }
120
+ return sum % 10 === 0;
121
+ }
91
122
  var ENTROPY_THRESHOLD = 4.5;
92
123
  var ENTROPY_MIN_LENGTH = 32;
93
124
  function extractHighEntropyTokens(input) {
@@ -113,6 +144,17 @@ function redactSensitiveData(input) {
113
144
  replaceAll(CUST_PATTERN, "CUST_ID");
114
145
  replaceAll(WALLET_ID_PATTERN, "WALLET_ID");
115
146
  replaceAll(VAULT_ID_PATTERN, "VAULT_ID");
147
+ redacted = redacted.replace(CARD_CANDIDATE_PATTERN, (match2) => {
148
+ const digits = match2.replace(/\D/g, "");
149
+ if (digits.length >= 13 && digits.length <= 19 && luhnValid(digits)) {
150
+ tokensReplaced.push(match2);
151
+ return "[REDACTED:CARD]";
152
+ }
153
+ return match2;
154
+ });
155
+ replaceAll(SSN_PATTERN, "SSN");
156
+ replaceAll(EMAIL_PATTERN, "EMAIL");
157
+ replaceAll(PHONE_PATTERN, "PHONE");
116
158
  const highEntropyTokens = extractHighEntropyTokens(redacted);
117
159
  for (const token of highEntropyTokens) {
118
160
  if (!redacted.includes(token)) continue;
@@ -130,9 +172,10 @@ var AgentShield = class {
130
172
  /**
131
173
  * Check a prompt against the policy engine before sending to the LLM.
132
174
  *
133
- * Applies local-first VPC redaction (BitGo) before sending to the gateway,
134
- * ensuring signing keys, custodial IDs, and high-entropy secrets never leave
135
- * the local process in plaintext.
175
+ * Applies best-effort local-first redaction before sending to the gateway, so
176
+ * recognized signing keys, custodial IDs, common PII, and high-entropy secrets
177
+ * are stripped before the prompt leaves the process. Redaction is one layer of
178
+ * defense, not a guarantee that every secret is caught (see redaction.ts).
136
179
  *
137
180
  * Returns the policy decision without executing the LLM call.
138
181
  */
@@ -211,12 +254,16 @@ var AgentShield = class {
211
254
  }
212
255
  /**
213
256
  * Log an interaction to the Agent Shield Console.
257
+ *
258
+ * Redacts the prompt before transmitting: the audit trail must not store or
259
+ * carry raw secrets/PII, and this is an egress point just like /check.
214
260
  */
215
261
  async log(input, result, requestId = newRequestId()) {
216
262
  const scopedLog = log.child({
217
263
  tenant_id: this.config.tenantId,
218
264
  request_id: requestId
219
265
  });
266
+ const { redacted } = redactSensitiveData(input);
220
267
  const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/log`, {
221
268
  method: "POST",
222
269
  headers: {
@@ -224,7 +271,7 @@ var AgentShield = class {
224
271
  Authorization: `Bearer ${this.config.apiKey}`
225
272
  },
226
273
  body: JSON.stringify({
227
- input,
274
+ input: redacted,
228
275
  tenantId: this.config.tenantId,
229
276
  requestId,
230
277
  userId: this.config.userId,
@@ -257,6 +304,9 @@ var ShieldBlockedError = class extends Error {
257
304
  // Annotate the CommonJS export names for ESM import in node:
258
305
  0 && (module.exports = {
259
306
  AgentShield,
260
- ShieldBlockedError
307
+ ShieldBlockedError,
308
+ newRequestId,
309
+ redactSensitiveData,
310
+ tenantId
261
311
  });
262
312
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/ids.ts","../src/logger.ts","../src/redaction.ts"],"sourcesContent":["/**\n * G8R Agent Shield SDK\n *\n * Lightweight TypeScript client that wraps LLM calls with policy enforcement.\n * Automatically intercepts prompts, applies local-first VPC redaction (BitGo),\n * checks them against the G8R policy engine, and logs all activity to the\n * Agent Shield Console.\n *\n * Usage:\n * import { AgentShield } from '@g8r-security/agent-shield-sdk';\n *\n * const shield = new AgentShield({\n * consoleUrl: 'https://shield.yourcompany.com',\n * apiKey: 'sk-shield-...',\n * department: 'Finance',\n * userId: 'usr_FIN_042',\n * aiModel: 'GPT-4o',\n * });\n *\n * // Wrap any LLM call — the factory function is only invoked if the policy allows it\n * const result = await shield.wrap(\n * () => openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Summarize Q1 earnings' }],\n * }),\n * 'Summarize Q1 earnings'\n * );\n */\n\nimport { match } from 'ts-pattern';\nimport { newRequestId, type RequestId, type TenantId } from './ids';\nimport { log } from './logger';\nimport { redactSensitiveData } from './redaction';\n\nexport interface ShieldConfig {\n /** URL of the G8R Agent Shield Console */\n consoleUrl: string;\n /** API key for authentication */\n apiKey: string;\n /** Tenant this SDK instance operates on behalf of. Required. */\n tenantId: TenantId;\n /** Department of the calling user */\n department: string;\n /** User identifier */\n userId: string;\n /** AI model being called */\n aiModel: string;\n /** Optional: agent identifier (defaults to \"sdk-client\") */\n agentId?: string;\n /** Optional: employee display name (defaults to userId) */\n employeeName?: string;\n}\n\nexport interface PolicyCheckResult {\n decision: 'allowed' | 'blocked' | 'escalated';\n reason: string;\n violatedRule: string | null;\n requiresApproval: boolean;\n /**\n * Set to true when a kill-switch rule fires (e.g. unauthorized partner data\n * access). Consumers should tear down the agent session in response.\n */\n sessionRevoked?: boolean;\n complianceMappings: Array<{\n regulation: string;\n controlId: string;\n controlName: string;\n description: string;\n }>;\n /**\n * Tokens that were redacted from the prompt before it reached the gateway.\n * Undefined when no tokens were redacted (clean prompt).\n * Populated by the BitGo VPC local-first redaction layer.\n */\n redactedTokens?: string[];\n}\n\nexport interface ShieldLogEntry {\n id: string;\n decision: string;\n timestamp: string;\n}\n\nexport class AgentShield {\n private config: ShieldConfig;\n\n constructor(config: ShieldConfig) {\n this.config = config;\n }\n\n /**\n * Check a prompt against the policy engine before sending to the LLM.\n *\n * Applies local-first VPC redaction (BitGo) before sending to the gateway,\n * ensuring signing keys, custodial IDs, and high-entropy secrets never leave\n * the local process in plaintext.\n *\n * Returns the policy decision without executing the LLM call.\n */\n async check(\n prompt: string,\n requestId: RequestId = newRequestId()\n ): Promise<PolicyCheckResult> {\n // Step 1: Local-first redaction — strip signing keys and custodial IDs\n // before the prompt reaches the remote gateway (BitGo VPC masking).\n const { redacted, tokensReplaced } = redactSensitiveData(prompt);\n\n // `requestId` is generated per-call by default, but `wrap()` passes its\n // own value so /check and /log share a single correlation id end-to-end\n // (C2). Build a scoped logger bound to tenantId + requestId so any\n // failure path emits lines with that context automatically.\n const scopedLog = log.child({\n tenant_id: this.config.tenantId,\n request_id: requestId,\n });\n\n const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/check`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({\n input: redacted, // send the redacted version — never the raw prompt\n tenantId: this.config.tenantId,\n requestId,\n userId: this.config.userId,\n department: this.config.department,\n aiModel: this.config.aiModel,\n agentId: this.config.agentId ?? 'sdk-client',\n }),\n });\n\n if (!res.ok) {\n scopedLog.error('Shield policy check failed', { status: res.status });\n throw new Error(`Shield policy check failed: ${res.status}`);\n }\n\n const result = await res.json();\n return {\n ...result,\n ...(tokensReplaced.length > 0 ? { redactedTokens: tokensReplaced } : {}),\n };\n }\n\n /**\n * Wrap an LLM call with policy enforcement.\n *\n * The `llmCallFactory` is a function that creates the LLM promise.\n * It is only invoked if the policy engine allows the action.\n * This prevents the LLM call from executing before the policy check completes.\n *\n * @param llmCallFactory - A function that returns the LLM call promise\n * @param prompt - The prompt text to evaluate against the policy engine\n * @returns The result of the LLM call if allowed\n * @throws ShieldBlockedError if the policy engine blocks the action\n *\n * @example\n * const result = await shield.wrap(\n * () => openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: prompt }],\n * }),\n * prompt\n * );\n */\n async wrap<T>(llmCallFactory: () => Promise<T>, prompt: string): Promise<T> {\n // Generate a single requestId for this wrap() invocation and thread it\n // through both /check and /log so the two server-side log lines can be\n // joined end-to-end (C2). Without this, each call would mint its own id\n // and the audit trail would lose the policy→action linkage.\n const requestId = newRequestId();\n\n // Step 1: Check the prompt against the policy engine (includes redaction)\n const policyResult = await this.check(prompt, requestId);\n\n // Step 2: Log the attempt regardless of decision\n await this.log(prompt, policyResult, requestId);\n\n // Step 3: Enforce the decision. Exhaustive match — adding a fourth\n // decision value will fail TypeScript compilation here until handled.\n match(policyResult.decision)\n .with('blocked', () => {\n throw new ShieldBlockedError(\n policyResult.reason,\n policyResult.violatedRule,\n policyResult.complianceMappings,\n policyResult.sessionRevoked ?? false\n );\n })\n .with('escalated', () => {\n // In production, this would await human approval via webhook\n log.warn('Action escalated for human review', {\n reason: policyResult.reason,\n });\n })\n .with('allowed', () => {\n /* fall through to invoke the LLM */\n })\n .exhaustive();\n\n // Step 4: Only now invoke the LLM call — after policy check passed\n return llmCallFactory();\n }\n\n /**\n * Log an interaction to the Agent Shield Console.\n */\n private async log(\n input: string,\n result: PolicyCheckResult,\n requestId: RequestId = newRequestId()\n ): Promise<ShieldLogEntry> {\n const scopedLog = log.child({\n tenant_id: this.config.tenantId,\n request_id: requestId,\n });\n\n const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/log`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({\n input,\n tenantId: this.config.tenantId,\n requestId,\n userId: this.config.userId,\n department: this.config.department,\n aiModel: this.config.aiModel,\n agentId: this.config.agentId ?? 'sdk-client',\n employeeName: this.config.employeeName ?? this.config.userId,\n decision: result.decision,\n reason: result.reason,\n violatedRule: result.violatedRule,\n requiresApproval: result.requiresApproval,\n complianceMappings: result.complianceMappings,\n }),\n });\n\n if (!res.ok) {\n scopedLog.error('Failed to log interaction', { status: res.status });\n }\n\n return res.json();\n }\n}\n\n/**\n * Error thrown when the policy engine blocks an LLM call.\n */\nexport class ShieldBlockedError extends Error {\n public readonly violatedRule: string | null;\n public readonly complianceMappings: PolicyCheckResult['complianceMappings'];\n public readonly sessionRevoked: boolean;\n\n constructor(\n reason: string,\n violatedRule: string | null,\n complianceMappings: PolicyCheckResult['complianceMappings'],\n sessionRevoked: boolean = false\n ) {\n super(`[G8R Shield BLOCKED] ${reason}`);\n this.name = 'ShieldBlockedError';\n this.violatedRule = violatedRule;\n this.complianceMappings = complianceMappings;\n this.sessionRevoked = sessionRevoked;\n }\n}\n","/**\n * Branded id types + constructors used by the SDK.\n *\n * Vendored from the engine's `types.ts` so the published\n * `@g8r-security/agent-shield-sdk` package carries no `@g8r-security/core` dependency — the\n * engine is private IP and must not be a public-package dependency. Only\n * the id types/helpers the SDK actually uses are copied here.\n */\n\n/**\n * Identifies a single tenant in the multi-tenant governance plane.\n * Branded so a raw string can't be passed where a TenantId is expected.\n */\nexport type TenantId = string & { readonly __brand: 'TenantId' };\n\n/** Per-request correlation ID. Generated client-side by the SDK. */\nexport type RequestId = string & { readonly __brand: 'RequestId' };\n\nconst TENANT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;\n\n/**\n * Cast a string to TenantId. Use at boundaries (config, env, request body).\n * Enforces the charset / length contract: 1-64 chars of `[a-z0-9-]`,\n * starting with `[a-z0-9]` — catches programmatic misuse (config typos,\n * hard-coded slugs that drift).\n */\nexport function tenantId(s: string): TenantId {\n if (!s) throw new Error('tenantId cannot be empty');\n if (!TENANT_ID_PATTERN.test(s)) {\n throw new Error(\n `tenantId must be 1-64 chars, [a-z0-9-], starting with [a-z0-9] (got ${JSON.stringify(s)})`\n );\n }\n return s as TenantId;\n}\n\n/** Generate a fresh request ID. Prefers crypto.randomUUID where available. */\nexport function newRequestId(): RequestId {\n // crypto.randomUUID is available in Node 19+ and all modern browsers.\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID() as RequestId;\n }\n // Fallback: timestamp + Math.random (best-effort, not crypto-strong).\n return `req-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}` as RequestId;\n}\n","/**\n * G8R structured JSON logger.\n *\n * Tiny, dependency-free logger that emits one JSON object per line. Designed\n * for governance contexts where each log line must carry `tenant_id` and\n * `request_id` so downstream pipelines can route audit trails per-tenant.\n *\n * Vendored into the SDK (a verbatim copy of the engine's logger) so the\n * published `@g8r-security/agent-shield-sdk` package carries no `@g8r-security/core`\n * dependency — the engine is private IP and must not be a public-package\n * dependency.\n *\n * Use the default `log` singleton for app-level chatter; call `createLogger`\n * (or `.child()`) when you need per-request bindings injected.\n */\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nexport interface LogContext {\n tenant_id?: string;\n request_id?: string;\n [key: string]: unknown;\n}\n\nexport interface Logger {\n debug(msg: string, ctx?: LogContext): void;\n info(msg: string, ctx?: LogContext): void;\n warn(msg: string, ctx?: LogContext): void;\n error(msg: string, ctx?: LogContext): void;\n child(ctx: LogContext): Logger;\n}\n\nexport interface LoggerOptions {\n /** Defaults to 'info'. Anything below is dropped. */\n level?: LogLevel;\n /** Defaults to console.log. Override for testing or transport injection. */\n sink?: (line: string) => void;\n /** Context merged into every log line. */\n bindings?: LogContext;\n}\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n};\n\nexport function createLogger(opts: LoggerOptions = {}): Logger {\n const minLevel = LEVEL_ORDER[opts.level ?? 'info'];\n const sink = opts.sink ?? ((line: string) => console.log(line));\n const bindings = opts.bindings ?? {};\n\n function emit(level: LogLevel, msg: string, ctx?: LogContext): void {\n if (LEVEL_ORDER[level] < minLevel) return;\n const payload = {\n level,\n msg,\n ts: new Date().toISOString(),\n ...bindings,\n ...ctx,\n };\n sink(JSON.stringify(payload));\n }\n\n return {\n debug: (msg, ctx) => emit('debug', msg, ctx),\n info: (msg, ctx) => emit('info', msg, ctx),\n warn: (msg, ctx) => emit('warn', msg, ctx),\n error: (msg, ctx) => emit('error', msg, ctx),\n child: (extra) => createLogger({ ...opts, bindings: { ...bindings, ...extra } }),\n };\n}\n\n/** Default singleton — convenient for apps that don't need DI. */\nexport const log = createLogger();\n","/**\n * BitGo VPC Sensitive Data Redaction\n *\n * Redacts cryptographic keys, custodial identifiers, and high-entropy strings\n * BEFORE sending prompts to the G8R policy gateway (local-first redaction layer).\n *\n * Compliance:\n * - GDPR Art. 32: Security of Processing — appropriate technical measures\n * - PCI-DSS 3.4: Render PAN/sensitive data unreadable wherever stored or transmitted\n */\n\nexport interface RedactionResult {\n /** The input with all sensitive tokens replaced by placeholder strings. */\n redacted: string;\n /** The original sensitive token strings that were replaced. */\n tokensReplaced: string[];\n}\n\n/**\n * Shannon entropy: calculates bits per character.\n * High entropy (> 4.5) indicates likely cryptographic material.\n */\nfunction shannonEntropy(str: string): number {\n const freq = new Map<string, number>();\n for (const ch of str) {\n freq.set(ch, (freq.get(ch) ?? 0) + 1);\n }\n let entropy = 0;\n const len = str.length;\n for (const count of freq.values()) {\n const p = count / len;\n entropy -= p * Math.log2(p);\n }\n return entropy;\n}\n\n// ── Signing Key Patterns ─────────────────────────────────────────────────────\n\n/** BIP-32 extended public/private keys: xpub, xprv, ypub, zpub, zprv, etc. */\nconst BIP32_PATTERN = /\\b[xyz](?:pub|prv)[a-zA-Z0-9]{99,111}\\b/g;\n\n/**\n * WIF (Wallet Import Format) private keys.\n * Base58Check encoded, starts with 5 (uncompressed) or K/L (compressed), 51–52 chars.\n */\nconst WIF_PATTERN = /\\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\\b/g;\n\n/**\n * Raw hex 256-bit keys — exactly 64 hex characters, optionally 0x-prefixed.\n * Matches Ethereum private keys, secp256k1 scalars, etc.\n */\nconst HEX_KEY_PATTERN = /\\b(?:0x)?[0-9a-fA-F]{64}\\b/g;\n\n/** PEM-encoded private or public key blocks (multi-line). */\nconst PEM_PATTERN =\n /-----BEGIN (?:RSA |EC |OPENSSH )?(?:PRIVATE|PUBLIC) KEY-----[\\s\\S]*?-----END (?:RSA |EC |OPENSSH )?(?:PRIVATE|PUBLIC) KEY-----/g;\n\n// ── Custodial ID Patterns ─────────────────────────────────────────────────────\n\n/** BitGo custodial-id format: `custodial-id:abc123xyz` */\nconst CUSTODIAL_ID_PATTERN = /\\bcustodial-id:[A-Za-z0-9_-]+\\b/g;\n\n/** Short custodial references: `cust-98765` */\nconst CUST_PATTERN = /\\bcust-\\d+\\b/gi;\n\n/** Wallet identifiers: `wallet-id:wlt-abc999` */\nconst WALLET_ID_PATTERN = /\\bwallet-id:[A-Za-z0-9_-]+\\b/g;\n\n/** Vault identifiers: `vault-id:v-secure-001` */\nconst VAULT_ID_PATTERN = /\\bvault-id:[A-Za-z0-9_-]+\\b/g;\n\n// ── Entropy Detection Constants ───────────────────────────────────────────────\n\n/** Minimum Shannon entropy (bits/char) to flag a token as likely cryptographic material. */\nconst ENTROPY_THRESHOLD = 4.5;\n\n/** Minimum token length to apply entropy analysis (shorter strings are too ambiguous). */\nconst ENTROPY_MIN_LENGTH = 32;\n\n/**\n * Find tokens in the string that exceed the entropy threshold.\n * Splits on whitespace and common delimiters to isolate candidates.\n */\nfunction extractHighEntropyTokens(input: string): string[] {\n const candidates = input.split(/[\\s,;:\"'`(){}[\\]<>]+/).filter(Boolean);\n return candidates.filter(\n (token) => token.length >= ENTROPY_MIN_LENGTH && shannonEntropy(token) >= ENTROPY_THRESHOLD\n );\n}\n\n/**\n * Redact sensitive data from a prompt string before it reaches the gateway.\n *\n * Processing order (important — PEM first to avoid splitting on inner patterns):\n * 1. PEM private/public key blocks\n * 2. BIP-32 extended keys\n * 3. WIF private keys\n * 4. Raw hex 256-bit keys\n * 5. Custodial IDs (all four variants)\n * 6. High-entropy string catch-all\n */\nexport function redactSensitiveData(input: string): RedactionResult {\n const tokensReplaced: string[] = [];\n let redacted = input;\n\n function replaceAll(pattern: RegExp, label: string): void {\n redacted = redacted.replace(pattern, (match) => {\n tokensReplaced.push(match);\n return `[REDACTED:${label}]`;\n });\n }\n\n replaceAll(PEM_PATTERN, 'PEM_KEY');\n replaceAll(BIP32_PATTERN, 'BIP32_KEY');\n replaceAll(WIF_PATTERN, 'WIF_KEY');\n replaceAll(HEX_KEY_PATTERN, 'HEX_KEY');\n replaceAll(CUSTODIAL_ID_PATTERN, 'CUSTODIAL_ID');\n replaceAll(CUST_PATTERN, 'CUST_ID');\n replaceAll(WALLET_ID_PATTERN, 'WALLET_ID');\n replaceAll(VAULT_ID_PATTERN, 'VAULT_ID');\n\n // High-entropy catch-all — runs on the already-redacted string to avoid double-replacing.\n const highEntropyTokens = extractHighEntropyTokens(redacted);\n for (const token of highEntropyTokens) {\n if (!redacted.includes(token)) continue;\n tokensReplaced.push(token);\n redacted = redacted.split(token).join('[REDACTED:HIGH_ENTROPY]');\n }\n\n return { redacted, tokensReplaced };\n}\n"],"mappings":"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","names":["match"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/ids.ts","../src/logger.ts","../src/redaction.ts"],"sourcesContent":["/**\n * G8R Agent Shield SDK\n *\n * Lightweight TypeScript client that wraps LLM calls with policy enforcement.\n * Automatically intercepts prompts, applies best-effort local-first redaction,\n * checks them against the G8R policy engine, and logs all activity to the\n * Agent Shield Console.\n *\n * Usage:\n * import { AgentShield, tenantId } from '@g8r-security/agent-shield-sdk';\n *\n * const shield = new AgentShield({\n * consoleUrl: 'https://shield.yourcompany.com',\n * apiKey: 'sk-shield-...',\n * tenantId: tenantId('acme-corp'),\n * department: 'Finance',\n * userId: 'usr_FIN_042',\n * aiModel: 'GPT-4o',\n * });\n *\n * // Wrap any LLM call — the factory function is only invoked if the policy allows it\n * const result = await shield.wrap(\n * () => openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Summarize Q1 earnings' }],\n * }),\n * 'Summarize Q1 earnings'\n * );\n */\n\nimport { match } from 'ts-pattern';\nimport { newRequestId, type RequestId, type TenantId } from './ids';\nimport { log } from './logger';\nimport { redactSensitiveData } from './redaction';\n\n// ── Public API re-exports ────────────────────────────────────────────────────\n// Consumers need `tenantId()` to construct the branded TenantId required by\n// ShieldConfig, and `redactSensitiveData` is documented as a public helper.\nexport { tenantId, newRequestId } from './ids';\nexport type { TenantId, RequestId } from './ids';\nexport { redactSensitiveData } from './redaction';\nexport type { RedactionResult } from './redaction';\n\nexport interface ShieldConfig {\n /** URL of the G8R Agent Shield Console */\n consoleUrl: string;\n /** API key for authentication */\n apiKey: string;\n /** Tenant this SDK instance operates on behalf of. Required. */\n tenantId: TenantId;\n /** Department of the calling user */\n department: string;\n /** User identifier */\n userId: string;\n /** AI model being called */\n aiModel: string;\n /** Optional: agent identifier (defaults to \"sdk-client\") */\n agentId?: string;\n /** Optional: employee display name (defaults to userId) */\n employeeName?: string;\n}\n\nexport interface PolicyCheckResult {\n decision: 'allowed' | 'blocked' | 'escalated';\n reason: string;\n violatedRule: string | null;\n requiresApproval: boolean;\n /**\n * Set to true when a kill-switch rule fires (e.g. unauthorized partner data\n * access). Consumers should tear down the agent session in response.\n */\n sessionRevoked?: boolean;\n complianceMappings: Array<{\n regulation: string;\n controlId: string;\n controlName: string;\n description: string;\n }>;\n /**\n * Tokens that were redacted from the prompt before it reached the gateway.\n * Undefined when no tokens were redacted (clean prompt).\n * Populated by the local-first redaction layer.\n */\n redactedTokens?: string[];\n}\n\nexport interface ShieldLogEntry {\n id: string;\n decision: string;\n timestamp: string;\n}\n\nexport class AgentShield {\n private config: ShieldConfig;\n\n constructor(config: ShieldConfig) {\n this.config = config;\n }\n\n /**\n * Check a prompt against the policy engine before sending to the LLM.\n *\n * Applies best-effort local-first redaction before sending to the gateway, so\n * recognized signing keys, custodial IDs, common PII, and high-entropy secrets\n * are stripped before the prompt leaves the process. Redaction is one layer of\n * defense, not a guarantee that every secret is caught (see redaction.ts).\n *\n * Returns the policy decision without executing the LLM call.\n */\n async check(\n prompt: string,\n requestId: RequestId = newRequestId()\n ): Promise<PolicyCheckResult> {\n // Step 1: Local-first redaction — strip recognized secrets and PII\n // before the prompt reaches the remote gateway.\n const { redacted, tokensReplaced } = redactSensitiveData(prompt);\n\n // `requestId` is generated per-call by default, but `wrap()` passes its\n // own value so /check and /log share a single correlation id end-to-end\n // (C2). Build a scoped logger bound to tenantId + requestId so any\n // failure path emits lines with that context automatically.\n const scopedLog = log.child({\n tenant_id: this.config.tenantId,\n request_id: requestId,\n });\n\n const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/check`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({\n input: redacted, // send the redacted version — never the raw prompt\n tenantId: this.config.tenantId,\n requestId,\n userId: this.config.userId,\n department: this.config.department,\n aiModel: this.config.aiModel,\n agentId: this.config.agentId ?? 'sdk-client',\n }),\n });\n\n if (!res.ok) {\n scopedLog.error('Shield policy check failed', { status: res.status });\n throw new Error(`Shield policy check failed: ${res.status}`);\n }\n\n const result = await res.json();\n return {\n ...result,\n ...(tokensReplaced.length > 0 ? { redactedTokens: tokensReplaced } : {}),\n };\n }\n\n /**\n * Wrap an LLM call with policy enforcement.\n *\n * The `llmCallFactory` is a function that creates the LLM promise.\n * It is only invoked if the policy engine allows the action.\n * This prevents the LLM call from executing before the policy check completes.\n *\n * @param llmCallFactory - A function that returns the LLM call promise\n * @param prompt - The prompt text to evaluate against the policy engine\n * @returns The result of the LLM call if allowed\n * @throws ShieldBlockedError if the policy engine blocks the action\n *\n * @example\n * const result = await shield.wrap(\n * () => openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: prompt }],\n * }),\n * prompt\n * );\n */\n async wrap<T>(llmCallFactory: () => Promise<T>, prompt: string): Promise<T> {\n // Generate a single requestId for this wrap() invocation and thread it\n // through both /check and /log so the two server-side log lines can be\n // joined end-to-end (C2). Without this, each call would mint its own id\n // and the audit trail would lose the policy→action linkage.\n const requestId = newRequestId();\n\n // Step 1: Check the prompt against the policy engine (includes redaction)\n const policyResult = await this.check(prompt, requestId);\n\n // Step 2: Log the attempt regardless of decision. log() redacts before\n // transmitting, so the audit-log path never leaks raw secrets either.\n await this.log(prompt, policyResult, requestId);\n\n // Step 3: Enforce the decision. Exhaustive match — adding a fourth\n // decision value will fail TypeScript compilation here until handled.\n match(policyResult.decision)\n .with('blocked', () => {\n throw new ShieldBlockedError(\n policyResult.reason,\n policyResult.violatedRule,\n policyResult.complianceMappings,\n policyResult.sessionRevoked ?? false\n );\n })\n .with('escalated', () => {\n // In production, this would await human approval via webhook\n log.warn('Action escalated for human review', {\n reason: policyResult.reason,\n });\n })\n .with('allowed', () => {\n /* fall through to invoke the LLM */\n })\n .exhaustive();\n\n // Step 4: Only now invoke the LLM call — after policy check passed\n return llmCallFactory();\n }\n\n /**\n * Log an interaction to the Agent Shield Console.\n *\n * Redacts the prompt before transmitting: the audit trail must not store or\n * carry raw secrets/PII, and this is an egress point just like /check.\n */\n private async log(\n input: string,\n result: PolicyCheckResult,\n requestId: RequestId = newRequestId()\n ): Promise<ShieldLogEntry> {\n const scopedLog = log.child({\n tenant_id: this.config.tenantId,\n request_id: requestId,\n });\n\n // Redact at the egress boundary — never send the raw prompt to /log.\n const { redacted } = redactSensitiveData(input);\n\n const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/log`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({\n input: redacted,\n tenantId: this.config.tenantId,\n requestId,\n userId: this.config.userId,\n department: this.config.department,\n aiModel: this.config.aiModel,\n agentId: this.config.agentId ?? 'sdk-client',\n employeeName: this.config.employeeName ?? this.config.userId,\n decision: result.decision,\n reason: result.reason,\n violatedRule: result.violatedRule,\n requiresApproval: result.requiresApproval,\n complianceMappings: result.complianceMappings,\n }),\n });\n\n if (!res.ok) {\n scopedLog.error('Failed to log interaction', { status: res.status });\n }\n\n return res.json();\n }\n}\n\n/**\n * Error thrown when the policy engine blocks an LLM call.\n */\nexport class ShieldBlockedError extends Error {\n public readonly violatedRule: string | null;\n public readonly complianceMappings: PolicyCheckResult['complianceMappings'];\n public readonly sessionRevoked: boolean;\n\n constructor(\n reason: string,\n violatedRule: string | null,\n complianceMappings: PolicyCheckResult['complianceMappings'],\n sessionRevoked: boolean = false\n ) {\n super(`[G8R Shield BLOCKED] ${reason}`);\n this.name = 'ShieldBlockedError';\n this.violatedRule = violatedRule;\n this.complianceMappings = complianceMappings;\n this.sessionRevoked = sessionRevoked;\n }\n}\n","/**\n * Branded id types + constructors used by the SDK.\n *\n * Self-contained in the SDK so the published `@g8r-security/agent-shield-sdk`\n * package has no dependency on internal packages. Only the id types/helpers the\n * SDK actually uses are included here.\n */\n\n/**\n * Identifies a single tenant in the multi-tenant governance plane.\n * Branded so a raw string can't be passed where a TenantId is expected.\n */\nexport type TenantId = string & { readonly __brand: 'TenantId' };\n\n/** Per-request correlation ID. Generated client-side by the SDK. */\nexport type RequestId = string & { readonly __brand: 'RequestId' };\n\nconst TENANT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;\n\n/**\n * Cast a string to TenantId. Use at boundaries (config, env, request body).\n * Enforces the charset / length contract: 1-64 chars of `[a-z0-9-]`,\n * starting with `[a-z0-9]` — catches programmatic misuse (config typos,\n * hard-coded slugs that drift).\n */\nexport function tenantId(s: string): TenantId {\n if (!s) throw new Error('tenantId cannot be empty');\n if (!TENANT_ID_PATTERN.test(s)) {\n throw new Error(\n `tenantId must be 1-64 chars, [a-z0-9-], starting with [a-z0-9] (got ${JSON.stringify(s)})`\n );\n }\n return s as TenantId;\n}\n\n/** Generate a fresh request ID. Prefers crypto.randomUUID where available. */\nexport function newRequestId(): RequestId {\n // crypto.randomUUID is available in Node 19+ and all modern browsers.\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID() as RequestId;\n }\n // Fallback: timestamp + Math.random (best-effort, not crypto-strong).\n return `req-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}` as RequestId;\n}\n","/**\n * G8R structured JSON logger.\n *\n * Tiny, dependency-free logger that emits one JSON object per line. Designed\n * for governance contexts where each log line must carry `tenant_id` and\n * `request_id` so downstream pipelines can route audit trails per-tenant.\n *\n * Self-contained in the SDK so the published `@g8r-security/agent-shield-sdk`\n * package has no dependency on internal packages.\n *\n * Use the default `log` singleton for app-level chatter; call `createLogger`\n * (or `.child()`) when you need per-request bindings injected.\n */\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nexport interface LogContext {\n tenant_id?: string;\n request_id?: string;\n [key: string]: unknown;\n}\n\nexport interface Logger {\n debug(msg: string, ctx?: LogContext): void;\n info(msg: string, ctx?: LogContext): void;\n warn(msg: string, ctx?: LogContext): void;\n error(msg: string, ctx?: LogContext): void;\n child(ctx: LogContext): Logger;\n}\n\nexport interface LoggerOptions {\n /** Defaults to 'info'. Anything below is dropped. */\n level?: LogLevel;\n /** Defaults to console.log. Override for testing or transport injection. */\n sink?: (line: string) => void;\n /** Context merged into every log line. */\n bindings?: LogContext;\n}\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n};\n\nexport function createLogger(opts: LoggerOptions = {}): Logger {\n const minLevel = LEVEL_ORDER[opts.level ?? 'info'];\n const sink = opts.sink ?? ((line: string) => console.log(line));\n const bindings = opts.bindings ?? {};\n\n function emit(level: LogLevel, msg: string, ctx?: LogContext): void {\n if (LEVEL_ORDER[level] < minLevel) return;\n const payload = {\n level,\n msg,\n ts: new Date().toISOString(),\n ...bindings,\n ...ctx,\n };\n sink(JSON.stringify(payload));\n }\n\n return {\n debug: (msg, ctx) => emit('debug', msg, ctx),\n info: (msg, ctx) => emit('info', msg, ctx),\n warn: (msg, ctx) => emit('warn', msg, ctx),\n error: (msg, ctx) => emit('error', msg, ctx),\n child: (extra) => createLogger({ ...opts, bindings: { ...bindings, ...extra } }),\n };\n}\n\n/** Default singleton — convenient for apps that don't need DI. */\nexport const log = createLogger();\n","/**\n * Local-First Sensitive Data Redaction\n *\n * Best-effort redaction of common secret and PII formats BEFORE prompts reach\n * the policy gateway. Covers cryptographic key formats, custodial identifiers,\n * high-entropy strings, and common PII (card numbers validated via Luhn, US\n * SSNs, email addresses, and phone numbers).\n *\n * IMPORTANT — this is a defense-in-depth layer, NOT a guarantee of completeness.\n * Pattern- and entropy-based redaction cannot catch every secret or PII shape\n * (e.g. unstructured PII, names, novel token formats, or values below the\n * entropy threshold). Do not rely on it as a sole control; keep downstream\n * safeguards and human review in place.\n *\n * It helps *support* (does not by itself satisfy) controls such as GDPR Art. 32\n * and PCI-DSS PAN-handling by reducing sensitive-data exposure to the gateway.\n */\n\nexport interface RedactionResult {\n /** The input with all sensitive tokens replaced by placeholder strings. */\n redacted: string;\n /** The original sensitive token strings that were replaced. */\n tokensReplaced: string[];\n}\n\n/**\n * Shannon entropy: calculates bits per character.\n * High entropy (> 4.5) indicates likely cryptographic material.\n */\nfunction shannonEntropy(str: string): number {\n const freq = new Map<string, number>();\n for (const ch of str) {\n freq.set(ch, (freq.get(ch) ?? 0) + 1);\n }\n let entropy = 0;\n const len = str.length;\n for (const count of freq.values()) {\n const p = count / len;\n entropy -= p * Math.log2(p);\n }\n return entropy;\n}\n\n// ── Signing Key Patterns ─────────────────────────────────────────────────────\n\n/** BIP-32 extended public/private keys: xpub, xprv, ypub, zpub, zprv, etc. */\nconst BIP32_PATTERN = /\\b[xyz](?:pub|prv)[a-zA-Z0-9]{99,111}\\b/g;\n\n/**\n * WIF (Wallet Import Format) private keys.\n * Base58Check encoded, starts with 5 (uncompressed) or K/L (compressed), 51–52 chars.\n */\nconst WIF_PATTERN = /\\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\\b/g;\n\n/**\n * Raw hex 256-bit keys — exactly 64 hex characters, optionally 0x-prefixed.\n * Matches Ethereum private keys, secp256k1 scalars, etc.\n */\nconst HEX_KEY_PATTERN = /\\b(?:0x)?[0-9a-fA-F]{64}\\b/g;\n\n/** PEM-encoded private or public key blocks (multi-line). */\nconst PEM_PATTERN =\n /-----BEGIN (?:RSA |EC |OPENSSH )?(?:PRIVATE|PUBLIC) KEY-----[\\s\\S]*?-----END (?:RSA |EC |OPENSSH )?(?:PRIVATE|PUBLIC) KEY-----/g;\n\n// ── Custodial ID Patterns ─────────────────────────────────────────────────────\n\n/** Custodial-id format: `custodial-id:abc123xyz` */\nconst CUSTODIAL_ID_PATTERN = /\\bcustodial-id:[A-Za-z0-9_-]+\\b/g;\n\n/** Short custodial references: `cust-98765` */\nconst CUST_PATTERN = /\\bcust-\\d+\\b/gi;\n\n/** Wallet identifiers: `wallet-id:wlt-abc999` */\nconst WALLET_ID_PATTERN = /\\bwallet-id:[A-Za-z0-9_-]+\\b/g;\n\n/** Vault identifiers: `vault-id:v-secure-001` */\nconst VAULT_ID_PATTERN = /\\bvault-id:[A-Za-z0-9_-]+\\b/g;\n\n// ── PII Patterns (best-effort) ────────────────────────────────────────────────\n\n/** Email addresses. */\nconst EMAIL_PATTERN = /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/g;\n\n/** US Social Security Numbers in dashed or spaced form, e.g. `123-45-6789`. */\nconst SSN_PATTERN = /\\b\\d{3}[ -]\\d{2}[ -]\\d{4}\\b/g;\n\n/**\n * Phone numbers with explicit separators (avoids matching arbitrary digit runs).\n * Optional country code, then 3-3-4 with space/dot/hyphen between groups.\n */\nconst PHONE_PATTERN = /\\b(?:\\+?\\d{1,3}[ .-]?)?\\(?\\d{3}\\)?[ .-]\\d{3}[ .-]\\d{4}\\b/g;\n\n/**\n * Candidate card-number runs: 13–19 digits with optional single space/hyphen\n * separators. Validated with Luhn before redacting to avoid false positives on\n * arbitrary long numbers (order/invoice IDs, etc.).\n */\nconst CARD_CANDIDATE_PATTERN = /\\b\\d(?:[ -]?\\d){12,18}\\b/g;\n\n/**\n * Luhn checksum — used to gate card-number redaction so we only mask digit runs\n * that actually pass the check digit, not every long number.\n */\nfunction luhnValid(digits: string): boolean {\n let sum = 0;\n let double = false;\n for (let i = digits.length - 1; i >= 0; i--) {\n let d = digits.charCodeAt(i) - 48;\n if (double) {\n d *= 2;\n if (d > 9) d -= 9;\n }\n sum += d;\n double = !double;\n }\n return sum % 10 === 0;\n}\n\n// ── Entropy Detection Constants ───────────────────────────────────────────────\n\n/** Minimum Shannon entropy (bits/char) to flag a token as likely cryptographic material. */\nconst ENTROPY_THRESHOLD = 4.5;\n\n/** Minimum token length to apply entropy analysis (shorter strings are too ambiguous). */\nconst ENTROPY_MIN_LENGTH = 32;\n\n/**\n * Find tokens in the string that exceed the entropy threshold.\n * Splits on whitespace and common delimiters to isolate candidates.\n */\nfunction extractHighEntropyTokens(input: string): string[] {\n const candidates = input.split(/[\\s,;:\"'`(){}[\\]<>]+/).filter(Boolean);\n return candidates.filter(\n (token) => token.length >= ENTROPY_MIN_LENGTH && shannonEntropy(token) >= ENTROPY_THRESHOLD\n );\n}\n\n/**\n * Redact sensitive data from a prompt string before it reaches the gateway.\n *\n * Processing order (important — PEM first to avoid splitting on inner patterns):\n * 1. PEM private/public key blocks\n * 2. BIP-32 extended keys\n * 3. WIF private keys\n * 4. Raw hex 256-bit keys\n * 5. Custodial IDs (all four variants)\n * 6. PII — card numbers (Luhn-validated), SSNs, emails, phone numbers\n * 7. High-entropy string catch-all\n */\nexport function redactSensitiveData(input: string): RedactionResult {\n const tokensReplaced: string[] = [];\n let redacted = input;\n\n function replaceAll(pattern: RegExp, label: string): void {\n redacted = redacted.replace(pattern, (match) => {\n tokensReplaced.push(match);\n return `[REDACTED:${label}]`;\n });\n }\n\n replaceAll(PEM_PATTERN, 'PEM_KEY');\n replaceAll(BIP32_PATTERN, 'BIP32_KEY');\n replaceAll(WIF_PATTERN, 'WIF_KEY');\n replaceAll(HEX_KEY_PATTERN, 'HEX_KEY');\n replaceAll(CUSTODIAL_ID_PATTERN, 'CUSTODIAL_ID');\n replaceAll(CUST_PATTERN, 'CUST_ID');\n replaceAll(WALLET_ID_PATTERN, 'WALLET_ID');\n replaceAll(VAULT_ID_PATTERN, 'VAULT_ID');\n\n // PII (best-effort). Card numbers first, gated by Luhn so we don't mask every\n // long digit run; then structured SSN / email / phone formats.\n redacted = redacted.replace(CARD_CANDIDATE_PATTERN, (match) => {\n const digits = match.replace(/\\D/g, '');\n if (digits.length >= 13 && digits.length <= 19 && luhnValid(digits)) {\n tokensReplaced.push(match);\n return '[REDACTED:CARD]';\n }\n return match;\n });\n replaceAll(SSN_PATTERN, 'SSN');\n replaceAll(EMAIL_PATTERN, 'EMAIL');\n replaceAll(PHONE_PATTERN, 'PHONE');\n\n // High-entropy catch-all — runs on the already-redacted string to avoid double-replacing.\n const highEntropyTokens = extractHighEntropyTokens(redacted);\n for (const token of highEntropyTokens) {\n if (!redacted.includes(token)) continue;\n tokensReplaced.push(token);\n redacted = redacted.split(token).join('[REDACTED:HIGH_ENTROPY]');\n }\n\n return { redacted, tokensReplaced };\n}\n"],"mappings":"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","names":["match"]}
package/dist/index.mjs CHANGED
@@ -2,6 +2,16 @@
2
2
  import { match } from "ts-pattern";
3
3
 
4
4
  // src/ids.ts
5
+ var TENANT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
6
+ function tenantId(s) {
7
+ if (!s) throw new Error("tenantId cannot be empty");
8
+ if (!TENANT_ID_PATTERN.test(s)) {
9
+ throw new Error(
10
+ `tenantId must be 1-64 chars, [a-z0-9-], starting with [a-z0-9] (got ${JSON.stringify(s)})`
11
+ );
12
+ }
13
+ return s;
14
+ }
5
15
  function newRequestId() {
6
16
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
7
17
  return crypto.randomUUID();
@@ -63,6 +73,24 @@ var CUSTODIAL_ID_PATTERN = /\bcustodial-id:[A-Za-z0-9_-]+\b/g;
63
73
  var CUST_PATTERN = /\bcust-\d+\b/gi;
64
74
  var WALLET_ID_PATTERN = /\bwallet-id:[A-Za-z0-9_-]+\b/g;
65
75
  var VAULT_ID_PATTERN = /\bvault-id:[A-Za-z0-9_-]+\b/g;
76
+ var EMAIL_PATTERN = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
77
+ var SSN_PATTERN = /\b\d{3}[ -]\d{2}[ -]\d{4}\b/g;
78
+ var PHONE_PATTERN = /\b(?:\+?\d{1,3}[ .-]?)?\(?\d{3}\)?[ .-]\d{3}[ .-]\d{4}\b/g;
79
+ var CARD_CANDIDATE_PATTERN = /\b\d(?:[ -]?\d){12,18}\b/g;
80
+ function luhnValid(digits) {
81
+ let sum = 0;
82
+ let double = false;
83
+ for (let i = digits.length - 1; i >= 0; i--) {
84
+ let d = digits.charCodeAt(i) - 48;
85
+ if (double) {
86
+ d *= 2;
87
+ if (d > 9) d -= 9;
88
+ }
89
+ sum += d;
90
+ double = !double;
91
+ }
92
+ return sum % 10 === 0;
93
+ }
66
94
  var ENTROPY_THRESHOLD = 4.5;
67
95
  var ENTROPY_MIN_LENGTH = 32;
68
96
  function extractHighEntropyTokens(input) {
@@ -88,6 +116,17 @@ function redactSensitiveData(input) {
88
116
  replaceAll(CUST_PATTERN, "CUST_ID");
89
117
  replaceAll(WALLET_ID_PATTERN, "WALLET_ID");
90
118
  replaceAll(VAULT_ID_PATTERN, "VAULT_ID");
119
+ redacted = redacted.replace(CARD_CANDIDATE_PATTERN, (match2) => {
120
+ const digits = match2.replace(/\D/g, "");
121
+ if (digits.length >= 13 && digits.length <= 19 && luhnValid(digits)) {
122
+ tokensReplaced.push(match2);
123
+ return "[REDACTED:CARD]";
124
+ }
125
+ return match2;
126
+ });
127
+ replaceAll(SSN_PATTERN, "SSN");
128
+ replaceAll(EMAIL_PATTERN, "EMAIL");
129
+ replaceAll(PHONE_PATTERN, "PHONE");
91
130
  const highEntropyTokens = extractHighEntropyTokens(redacted);
92
131
  for (const token of highEntropyTokens) {
93
132
  if (!redacted.includes(token)) continue;
@@ -105,9 +144,10 @@ var AgentShield = class {
105
144
  /**
106
145
  * Check a prompt against the policy engine before sending to the LLM.
107
146
  *
108
- * Applies local-first VPC redaction (BitGo) before sending to the gateway,
109
- * ensuring signing keys, custodial IDs, and high-entropy secrets never leave
110
- * the local process in plaintext.
147
+ * Applies best-effort local-first redaction before sending to the gateway, so
148
+ * recognized signing keys, custodial IDs, common PII, and high-entropy secrets
149
+ * are stripped before the prompt leaves the process. Redaction is one layer of
150
+ * defense, not a guarantee that every secret is caught (see redaction.ts).
111
151
  *
112
152
  * Returns the policy decision without executing the LLM call.
113
153
  */
@@ -186,12 +226,16 @@ var AgentShield = class {
186
226
  }
187
227
  /**
188
228
  * Log an interaction to the Agent Shield Console.
229
+ *
230
+ * Redacts the prompt before transmitting: the audit trail must not store or
231
+ * carry raw secrets/PII, and this is an egress point just like /check.
189
232
  */
190
233
  async log(input, result, requestId = newRequestId()) {
191
234
  const scopedLog = log.child({
192
235
  tenant_id: this.config.tenantId,
193
236
  request_id: requestId
194
237
  });
238
+ const { redacted } = redactSensitiveData(input);
195
239
  const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/log`, {
196
240
  method: "POST",
197
241
  headers: {
@@ -199,7 +243,7 @@ var AgentShield = class {
199
243
  Authorization: `Bearer ${this.config.apiKey}`
200
244
  },
201
245
  body: JSON.stringify({
202
- input,
246
+ input: redacted,
203
247
  tenantId: this.config.tenantId,
204
248
  requestId,
205
249
  userId: this.config.userId,
@@ -231,6 +275,9 @@ var ShieldBlockedError = class extends Error {
231
275
  };
232
276
  export {
233
277
  AgentShield,
234
- ShieldBlockedError
278
+ ShieldBlockedError,
279
+ newRequestId,
280
+ redactSensitiveData,
281
+ tenantId
235
282
  };
236
283
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/ids.ts","../src/logger.ts","../src/redaction.ts"],"sourcesContent":["/**\n * G8R Agent Shield SDK\n *\n * Lightweight TypeScript client that wraps LLM calls with policy enforcement.\n * Automatically intercepts prompts, applies local-first VPC redaction (BitGo),\n * checks them against the G8R policy engine, and logs all activity to the\n * Agent Shield Console.\n *\n * Usage:\n * import { AgentShield } from '@g8r-security/agent-shield-sdk';\n *\n * const shield = new AgentShield({\n * consoleUrl: 'https://shield.yourcompany.com',\n * apiKey: 'sk-shield-...',\n * department: 'Finance',\n * userId: 'usr_FIN_042',\n * aiModel: 'GPT-4o',\n * });\n *\n * // Wrap any LLM call — the factory function is only invoked if the policy allows it\n * const result = await shield.wrap(\n * () => openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Summarize Q1 earnings' }],\n * }),\n * 'Summarize Q1 earnings'\n * );\n */\n\nimport { match } from 'ts-pattern';\nimport { newRequestId, type RequestId, type TenantId } from './ids';\nimport { log } from './logger';\nimport { redactSensitiveData } from './redaction';\n\nexport interface ShieldConfig {\n /** URL of the G8R Agent Shield Console */\n consoleUrl: string;\n /** API key for authentication */\n apiKey: string;\n /** Tenant this SDK instance operates on behalf of. Required. */\n tenantId: TenantId;\n /** Department of the calling user */\n department: string;\n /** User identifier */\n userId: string;\n /** AI model being called */\n aiModel: string;\n /** Optional: agent identifier (defaults to \"sdk-client\") */\n agentId?: string;\n /** Optional: employee display name (defaults to userId) */\n employeeName?: string;\n}\n\nexport interface PolicyCheckResult {\n decision: 'allowed' | 'blocked' | 'escalated';\n reason: string;\n violatedRule: string | null;\n requiresApproval: boolean;\n /**\n * Set to true when a kill-switch rule fires (e.g. unauthorized partner data\n * access). Consumers should tear down the agent session in response.\n */\n sessionRevoked?: boolean;\n complianceMappings: Array<{\n regulation: string;\n controlId: string;\n controlName: string;\n description: string;\n }>;\n /**\n * Tokens that were redacted from the prompt before it reached the gateway.\n * Undefined when no tokens were redacted (clean prompt).\n * Populated by the BitGo VPC local-first redaction layer.\n */\n redactedTokens?: string[];\n}\n\nexport interface ShieldLogEntry {\n id: string;\n decision: string;\n timestamp: string;\n}\n\nexport class AgentShield {\n private config: ShieldConfig;\n\n constructor(config: ShieldConfig) {\n this.config = config;\n }\n\n /**\n * Check a prompt against the policy engine before sending to the LLM.\n *\n * Applies local-first VPC redaction (BitGo) before sending to the gateway,\n * ensuring signing keys, custodial IDs, and high-entropy secrets never leave\n * the local process in plaintext.\n *\n * Returns the policy decision without executing the LLM call.\n */\n async check(\n prompt: string,\n requestId: RequestId = newRequestId()\n ): Promise<PolicyCheckResult> {\n // Step 1: Local-first redaction — strip signing keys and custodial IDs\n // before the prompt reaches the remote gateway (BitGo VPC masking).\n const { redacted, tokensReplaced } = redactSensitiveData(prompt);\n\n // `requestId` is generated per-call by default, but `wrap()` passes its\n // own value so /check and /log share a single correlation id end-to-end\n // (C2). Build a scoped logger bound to tenantId + requestId so any\n // failure path emits lines with that context automatically.\n const scopedLog = log.child({\n tenant_id: this.config.tenantId,\n request_id: requestId,\n });\n\n const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/check`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({\n input: redacted, // send the redacted version — never the raw prompt\n tenantId: this.config.tenantId,\n requestId,\n userId: this.config.userId,\n department: this.config.department,\n aiModel: this.config.aiModel,\n agentId: this.config.agentId ?? 'sdk-client',\n }),\n });\n\n if (!res.ok) {\n scopedLog.error('Shield policy check failed', { status: res.status });\n throw new Error(`Shield policy check failed: ${res.status}`);\n }\n\n const result = await res.json();\n return {\n ...result,\n ...(tokensReplaced.length > 0 ? { redactedTokens: tokensReplaced } : {}),\n };\n }\n\n /**\n * Wrap an LLM call with policy enforcement.\n *\n * The `llmCallFactory` is a function that creates the LLM promise.\n * It is only invoked if the policy engine allows the action.\n * This prevents the LLM call from executing before the policy check completes.\n *\n * @param llmCallFactory - A function that returns the LLM call promise\n * @param prompt - The prompt text to evaluate against the policy engine\n * @returns The result of the LLM call if allowed\n * @throws ShieldBlockedError if the policy engine blocks the action\n *\n * @example\n * const result = await shield.wrap(\n * () => openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: prompt }],\n * }),\n * prompt\n * );\n */\n async wrap<T>(llmCallFactory: () => Promise<T>, prompt: string): Promise<T> {\n // Generate a single requestId for this wrap() invocation and thread it\n // through both /check and /log so the two server-side log lines can be\n // joined end-to-end (C2). Without this, each call would mint its own id\n // and the audit trail would lose the policy→action linkage.\n const requestId = newRequestId();\n\n // Step 1: Check the prompt against the policy engine (includes redaction)\n const policyResult = await this.check(prompt, requestId);\n\n // Step 2: Log the attempt regardless of decision\n await this.log(prompt, policyResult, requestId);\n\n // Step 3: Enforce the decision. Exhaustive match — adding a fourth\n // decision value will fail TypeScript compilation here until handled.\n match(policyResult.decision)\n .with('blocked', () => {\n throw new ShieldBlockedError(\n policyResult.reason,\n policyResult.violatedRule,\n policyResult.complianceMappings,\n policyResult.sessionRevoked ?? false\n );\n })\n .with('escalated', () => {\n // In production, this would await human approval via webhook\n log.warn('Action escalated for human review', {\n reason: policyResult.reason,\n });\n })\n .with('allowed', () => {\n /* fall through to invoke the LLM */\n })\n .exhaustive();\n\n // Step 4: Only now invoke the LLM call — after policy check passed\n return llmCallFactory();\n }\n\n /**\n * Log an interaction to the Agent Shield Console.\n */\n private async log(\n input: string,\n result: PolicyCheckResult,\n requestId: RequestId = newRequestId()\n ): Promise<ShieldLogEntry> {\n const scopedLog = log.child({\n tenant_id: this.config.tenantId,\n request_id: requestId,\n });\n\n const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/log`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({\n input,\n tenantId: this.config.tenantId,\n requestId,\n userId: this.config.userId,\n department: this.config.department,\n aiModel: this.config.aiModel,\n agentId: this.config.agentId ?? 'sdk-client',\n employeeName: this.config.employeeName ?? this.config.userId,\n decision: result.decision,\n reason: result.reason,\n violatedRule: result.violatedRule,\n requiresApproval: result.requiresApproval,\n complianceMappings: result.complianceMappings,\n }),\n });\n\n if (!res.ok) {\n scopedLog.error('Failed to log interaction', { status: res.status });\n }\n\n return res.json();\n }\n}\n\n/**\n * Error thrown when the policy engine blocks an LLM call.\n */\nexport class ShieldBlockedError extends Error {\n public readonly violatedRule: string | null;\n public readonly complianceMappings: PolicyCheckResult['complianceMappings'];\n public readonly sessionRevoked: boolean;\n\n constructor(\n reason: string,\n violatedRule: string | null,\n complianceMappings: PolicyCheckResult['complianceMappings'],\n sessionRevoked: boolean = false\n ) {\n super(`[G8R Shield BLOCKED] ${reason}`);\n this.name = 'ShieldBlockedError';\n this.violatedRule = violatedRule;\n this.complianceMappings = complianceMappings;\n this.sessionRevoked = sessionRevoked;\n }\n}\n","/**\n * Branded id types + constructors used by the SDK.\n *\n * Vendored from the engine's `types.ts` so the published\n * `@g8r-security/agent-shield-sdk` package carries no `@g8r-security/core` dependency — the\n * engine is private IP and must not be a public-package dependency. Only\n * the id types/helpers the SDK actually uses are copied here.\n */\n\n/**\n * Identifies a single tenant in the multi-tenant governance plane.\n * Branded so a raw string can't be passed where a TenantId is expected.\n */\nexport type TenantId = string & { readonly __brand: 'TenantId' };\n\n/** Per-request correlation ID. Generated client-side by the SDK. */\nexport type RequestId = string & { readonly __brand: 'RequestId' };\n\nconst TENANT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;\n\n/**\n * Cast a string to TenantId. Use at boundaries (config, env, request body).\n * Enforces the charset / length contract: 1-64 chars of `[a-z0-9-]`,\n * starting with `[a-z0-9]` — catches programmatic misuse (config typos,\n * hard-coded slugs that drift).\n */\nexport function tenantId(s: string): TenantId {\n if (!s) throw new Error('tenantId cannot be empty');\n if (!TENANT_ID_PATTERN.test(s)) {\n throw new Error(\n `tenantId must be 1-64 chars, [a-z0-9-], starting with [a-z0-9] (got ${JSON.stringify(s)})`\n );\n }\n return s as TenantId;\n}\n\n/** Generate a fresh request ID. Prefers crypto.randomUUID where available. */\nexport function newRequestId(): RequestId {\n // crypto.randomUUID is available in Node 19+ and all modern browsers.\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID() as RequestId;\n }\n // Fallback: timestamp + Math.random (best-effort, not crypto-strong).\n return `req-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}` as RequestId;\n}\n","/**\n * G8R structured JSON logger.\n *\n * Tiny, dependency-free logger that emits one JSON object per line. Designed\n * for governance contexts where each log line must carry `tenant_id` and\n * `request_id` so downstream pipelines can route audit trails per-tenant.\n *\n * Vendored into the SDK (a verbatim copy of the engine's logger) so the\n * published `@g8r-security/agent-shield-sdk` package carries no `@g8r-security/core`\n * dependency — the engine is private IP and must not be a public-package\n * dependency.\n *\n * Use the default `log` singleton for app-level chatter; call `createLogger`\n * (or `.child()`) when you need per-request bindings injected.\n */\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nexport interface LogContext {\n tenant_id?: string;\n request_id?: string;\n [key: string]: unknown;\n}\n\nexport interface Logger {\n debug(msg: string, ctx?: LogContext): void;\n info(msg: string, ctx?: LogContext): void;\n warn(msg: string, ctx?: LogContext): void;\n error(msg: string, ctx?: LogContext): void;\n child(ctx: LogContext): Logger;\n}\n\nexport interface LoggerOptions {\n /** Defaults to 'info'. Anything below is dropped. */\n level?: LogLevel;\n /** Defaults to console.log. Override for testing or transport injection. */\n sink?: (line: string) => void;\n /** Context merged into every log line. */\n bindings?: LogContext;\n}\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n};\n\nexport function createLogger(opts: LoggerOptions = {}): Logger {\n const minLevel = LEVEL_ORDER[opts.level ?? 'info'];\n const sink = opts.sink ?? ((line: string) => console.log(line));\n const bindings = opts.bindings ?? {};\n\n function emit(level: LogLevel, msg: string, ctx?: LogContext): void {\n if (LEVEL_ORDER[level] < minLevel) return;\n const payload = {\n level,\n msg,\n ts: new Date().toISOString(),\n ...bindings,\n ...ctx,\n };\n sink(JSON.stringify(payload));\n }\n\n return {\n debug: (msg, ctx) => emit('debug', msg, ctx),\n info: (msg, ctx) => emit('info', msg, ctx),\n warn: (msg, ctx) => emit('warn', msg, ctx),\n error: (msg, ctx) => emit('error', msg, ctx),\n child: (extra) => createLogger({ ...opts, bindings: { ...bindings, ...extra } }),\n };\n}\n\n/** Default singleton — convenient for apps that don't need DI. */\nexport const log = createLogger();\n","/**\n * BitGo VPC Sensitive Data Redaction\n *\n * Redacts cryptographic keys, custodial identifiers, and high-entropy strings\n * BEFORE sending prompts to the G8R policy gateway (local-first redaction layer).\n *\n * Compliance:\n * - GDPR Art. 32: Security of Processing — appropriate technical measures\n * - PCI-DSS 3.4: Render PAN/sensitive data unreadable wherever stored or transmitted\n */\n\nexport interface RedactionResult {\n /** The input with all sensitive tokens replaced by placeholder strings. */\n redacted: string;\n /** The original sensitive token strings that were replaced. */\n tokensReplaced: string[];\n}\n\n/**\n * Shannon entropy: calculates bits per character.\n * High entropy (> 4.5) indicates likely cryptographic material.\n */\nfunction shannonEntropy(str: string): number {\n const freq = new Map<string, number>();\n for (const ch of str) {\n freq.set(ch, (freq.get(ch) ?? 0) + 1);\n }\n let entropy = 0;\n const len = str.length;\n for (const count of freq.values()) {\n const p = count / len;\n entropy -= p * Math.log2(p);\n }\n return entropy;\n}\n\n// ── Signing Key Patterns ─────────────────────────────────────────────────────\n\n/** BIP-32 extended public/private keys: xpub, xprv, ypub, zpub, zprv, etc. */\nconst BIP32_PATTERN = /\\b[xyz](?:pub|prv)[a-zA-Z0-9]{99,111}\\b/g;\n\n/**\n * WIF (Wallet Import Format) private keys.\n * Base58Check encoded, starts with 5 (uncompressed) or K/L (compressed), 51–52 chars.\n */\nconst WIF_PATTERN = /\\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\\b/g;\n\n/**\n * Raw hex 256-bit keys — exactly 64 hex characters, optionally 0x-prefixed.\n * Matches Ethereum private keys, secp256k1 scalars, etc.\n */\nconst HEX_KEY_PATTERN = /\\b(?:0x)?[0-9a-fA-F]{64}\\b/g;\n\n/** PEM-encoded private or public key blocks (multi-line). */\nconst PEM_PATTERN =\n /-----BEGIN (?:RSA |EC |OPENSSH )?(?:PRIVATE|PUBLIC) KEY-----[\\s\\S]*?-----END (?:RSA |EC |OPENSSH )?(?:PRIVATE|PUBLIC) KEY-----/g;\n\n// ── Custodial ID Patterns ─────────────────────────────────────────────────────\n\n/** BitGo custodial-id format: `custodial-id:abc123xyz` */\nconst CUSTODIAL_ID_PATTERN = /\\bcustodial-id:[A-Za-z0-9_-]+\\b/g;\n\n/** Short custodial references: `cust-98765` */\nconst CUST_PATTERN = /\\bcust-\\d+\\b/gi;\n\n/** Wallet identifiers: `wallet-id:wlt-abc999` */\nconst WALLET_ID_PATTERN = /\\bwallet-id:[A-Za-z0-9_-]+\\b/g;\n\n/** Vault identifiers: `vault-id:v-secure-001` */\nconst VAULT_ID_PATTERN = /\\bvault-id:[A-Za-z0-9_-]+\\b/g;\n\n// ── Entropy Detection Constants ───────────────────────────────────────────────\n\n/** Minimum Shannon entropy (bits/char) to flag a token as likely cryptographic material. */\nconst ENTROPY_THRESHOLD = 4.5;\n\n/** Minimum token length to apply entropy analysis (shorter strings are too ambiguous). */\nconst ENTROPY_MIN_LENGTH = 32;\n\n/**\n * Find tokens in the string that exceed the entropy threshold.\n * Splits on whitespace and common delimiters to isolate candidates.\n */\nfunction extractHighEntropyTokens(input: string): string[] {\n const candidates = input.split(/[\\s,;:\"'`(){}[\\]<>]+/).filter(Boolean);\n return candidates.filter(\n (token) => token.length >= ENTROPY_MIN_LENGTH && shannonEntropy(token) >= ENTROPY_THRESHOLD\n );\n}\n\n/**\n * Redact sensitive data from a prompt string before it reaches the gateway.\n *\n * Processing order (important — PEM first to avoid splitting on inner patterns):\n * 1. PEM private/public key blocks\n * 2. BIP-32 extended keys\n * 3. WIF private keys\n * 4. Raw hex 256-bit keys\n * 5. Custodial IDs (all four variants)\n * 6. High-entropy string catch-all\n */\nexport function redactSensitiveData(input: string): RedactionResult {\n const tokensReplaced: string[] = [];\n let redacted = input;\n\n function replaceAll(pattern: RegExp, label: string): void {\n redacted = redacted.replace(pattern, (match) => {\n tokensReplaced.push(match);\n return `[REDACTED:${label}]`;\n });\n }\n\n replaceAll(PEM_PATTERN, 'PEM_KEY');\n replaceAll(BIP32_PATTERN, 'BIP32_KEY');\n replaceAll(WIF_PATTERN, 'WIF_KEY');\n replaceAll(HEX_KEY_PATTERN, 'HEX_KEY');\n replaceAll(CUSTODIAL_ID_PATTERN, 'CUSTODIAL_ID');\n replaceAll(CUST_PATTERN, 'CUST_ID');\n replaceAll(WALLET_ID_PATTERN, 'WALLET_ID');\n replaceAll(VAULT_ID_PATTERN, 'VAULT_ID');\n\n // High-entropy catch-all — runs on the already-redacted string to avoid double-replacing.\n const highEntropyTokens = extractHighEntropyTokens(redacted);\n for (const token of highEntropyTokens) {\n if (!redacted.includes(token)) continue;\n tokensReplaced.push(token);\n redacted = redacted.split(token).join('[REDACTED:HIGH_ENTROPY]');\n }\n\n return { redacted, tokensReplaced };\n}\n"],"mappings":";AA6BA,SAAS,aAAa;;;ACQf,SAAS,eAA0B;AAExC,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY;AAC5E,WAAO,OAAO,WAAW;AAAA,EAC3B;AAEA,SAAO,OAAO,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAClF;;;ACHA,IAAM,cAAwC;AAAA,EAC5C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAEO,SAAS,aAAa,OAAsB,CAAC,GAAW;AAC7D,QAAM,WAAW,YAAY,KAAK,SAAS,MAAM;AACjD,QAAM,OAAO,KAAK,SAAS,CAAC,SAAiB,QAAQ,IAAI,IAAI;AAC7D,QAAM,WAAW,KAAK,YAAY,CAAC;AAEnC,WAAS,KAAK,OAAiB,KAAa,KAAwB;AAClE,QAAI,YAAY,KAAK,IAAI,SAAU;AACnC,UAAM,UAAU;AAAA,MACd;AAAA,MACA;AAAA,MACA,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3B,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AACA,SAAK,KAAK,UAAU,OAAO,CAAC;AAAA,EAC9B;AAEA,SAAO;AAAA,IACL,OAAO,CAAC,KAAK,QAAQ,KAAK,SAAS,KAAK,GAAG;AAAA,IAC3C,MAAM,CAAC,KAAK,QAAQ,KAAK,QAAQ,KAAK,GAAG;AAAA,IACzC,MAAM,CAAC,KAAK,QAAQ,KAAK,QAAQ,KAAK,GAAG;AAAA,IACzC,OAAO,CAAC,KAAK,QAAQ,KAAK,SAAS,KAAK,GAAG;AAAA,IAC3C,OAAO,CAAC,UAAU,aAAa,EAAE,GAAG,MAAM,UAAU,EAAE,GAAG,UAAU,GAAG,MAAM,EAAE,CAAC;AAAA,EACjF;AACF;AAGO,IAAM,MAAM,aAAa;;;ACrDhC,SAAS,eAAe,KAAqB;AAC3C,QAAM,OAAO,oBAAI,IAAoB;AACrC,aAAW,MAAM,KAAK;AACpB,SAAK,IAAI,KAAK,KAAK,IAAI,EAAE,KAAK,KAAK,CAAC;AAAA,EACtC;AACA,MAAI,UAAU;AACd,QAAM,MAAM,IAAI;AAChB,aAAW,SAAS,KAAK,OAAO,GAAG;AACjC,UAAM,IAAI,QAAQ;AAClB,eAAW,IAAI,KAAK,KAAK,CAAC;AAAA,EAC5B;AACA,SAAO;AACT;AAKA,IAAM,gBAAgB;AAMtB,IAAM,cAAc;AAMpB,IAAM,kBAAkB;AAGxB,IAAM,cACJ;AAKF,IAAM,uBAAuB;AAG7B,IAAM,eAAe;AAGrB,IAAM,oBAAoB;AAG1B,IAAM,mBAAmB;AAKzB,IAAM,oBAAoB;AAG1B,IAAM,qBAAqB;AAM3B,SAAS,yBAAyB,OAAyB;AACzD,QAAM,aAAa,MAAM,MAAM,sBAAsB,EAAE,OAAO,OAAO;AACrE,SAAO,WAAW;AAAA,IAChB,CAAC,UAAU,MAAM,UAAU,sBAAsB,eAAe,KAAK,KAAK;AAAA,EAC5E;AACF;AAaO,SAAS,oBAAoB,OAAgC;AAClE,QAAM,iBAA2B,CAAC;AAClC,MAAI,WAAW;AAEf,WAAS,WAAW,SAAiB,OAAqB;AACxD,eAAW,SAAS,QAAQ,SAAS,CAACA,WAAU;AAC9C,qBAAe,KAAKA,MAAK;AACzB,aAAO,aAAa,KAAK;AAAA,IAC3B,CAAC;AAAA,EACH;AAEA,aAAW,aAAa,SAAS;AACjC,aAAW,eAAe,WAAW;AACrC,aAAW,aAAa,SAAS;AACjC,aAAW,iBAAiB,SAAS;AACrC,aAAW,sBAAsB,cAAc;AAC/C,aAAW,cAAc,SAAS;AAClC,aAAW,mBAAmB,WAAW;AACzC,aAAW,kBAAkB,UAAU;AAGvC,QAAM,oBAAoB,yBAAyB,QAAQ;AAC3D,aAAW,SAAS,mBAAmB;AACrC,QAAI,CAAC,SAAS,SAAS,KAAK,EAAG;AAC/B,mBAAe,KAAK,KAAK;AACzB,eAAW,SAAS,MAAM,KAAK,EAAE,KAAK,yBAAyB;AAAA,EACjE;AAEA,SAAO,EAAE,UAAU,eAAe;AACpC;;;AH/CO,IAAM,cAAN,MAAkB;AAAA,EAGvB,YAAY,QAAsB;AAChC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,MACJ,QACA,YAAuB,aAAa,GACR;AAG5B,UAAM,EAAE,UAAU,eAAe,IAAI,oBAAoB,MAAM;AAM/D,UAAM,YAAY,IAAI,MAAM;AAAA,MAC1B,WAAW,KAAK,OAAO;AAAA,MACvB,YAAY;AAAA,IACd,CAAC;AAED,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,UAAU,qBAAqB;AAAA,MACpE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,eAAe,UAAU,KAAK,OAAO,MAAM;AAAA,MAC7C;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB,OAAO;AAAA;AAAA,QACP,UAAU,KAAK,OAAO;AAAA,QACtB;AAAA,QACA,QAAQ,KAAK,OAAO;AAAA,QACpB,YAAY,KAAK,OAAO;AAAA,QACxB,SAAS,KAAK,OAAO;AAAA,QACrB,SAAS,KAAK,OAAO,WAAW;AAAA,MAClC,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,gBAAU,MAAM,8BAA8B,EAAE,QAAQ,IAAI,OAAO,CAAC;AACpE,YAAM,IAAI,MAAM,+BAA+B,IAAI,MAAM,EAAE;AAAA,IAC7D;AAEA,UAAM,SAAS,MAAM,IAAI,KAAK;AAC9B,WAAO;AAAA,MACL,GAAG;AAAA,MACH,GAAI,eAAe,SAAS,IAAI,EAAE,gBAAgB,eAAe,IAAI,CAAC;AAAA,IACxE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,MAAM,KAAQ,gBAAkC,QAA4B;AAK1E,UAAM,YAAY,aAAa;AAG/B,UAAM,eAAe,MAAM,KAAK,MAAM,QAAQ,SAAS;AAGvD,UAAM,KAAK,IAAI,QAAQ,cAAc,SAAS;AAI9C,UAAM,aAAa,QAAQ,EACxB,KAAK,WAAW,MAAM;AACrB,YAAM,IAAI;AAAA,QACR,aAAa;AAAA,QACb,aAAa;AAAA,QACb,aAAa;AAAA,QACb,aAAa,kBAAkB;AAAA,MACjC;AAAA,IACF,CAAC,EACA,KAAK,aAAa,MAAM;AAEvB,UAAI,KAAK,qCAAqC;AAAA,QAC5C,QAAQ,aAAa;AAAA,MACvB,CAAC;AAAA,IACH,CAAC,EACA,KAAK,WAAW,MAAM;AAAA,IAEvB,CAAC,EACA,WAAW;AAGd,WAAO,eAAe;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,IACZ,OACA,QACA,YAAuB,aAAa,GACX;AACzB,UAAM,YAAY,IAAI,MAAM;AAAA,MAC1B,WAAW,KAAK,OAAO;AAAA,MACvB,YAAY;AAAA,IACd,CAAC;AAED,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,UAAU,mBAAmB;AAAA,MAClE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,eAAe,UAAU,KAAK,OAAO,MAAM;AAAA,MAC7C;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB;AAAA,QACA,UAAU,KAAK,OAAO;AAAA,QACtB;AAAA,QACA,QAAQ,KAAK,OAAO;AAAA,QACpB,YAAY,KAAK,OAAO;AAAA,QACxB,SAAS,KAAK,OAAO;AAAA,QACrB,SAAS,KAAK,OAAO,WAAW;AAAA,QAChC,cAAc,KAAK,OAAO,gBAAgB,KAAK,OAAO;AAAA,QACtD,UAAU,OAAO;AAAA,QACjB,QAAQ,OAAO;AAAA,QACf,cAAc,OAAO;AAAA,QACrB,kBAAkB,OAAO;AAAA,QACzB,oBAAoB,OAAO;AAAA,MAC7B,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,gBAAU,MAAM,6BAA6B,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACrE;AAEA,WAAO,IAAI,KAAK;AAAA,EAClB;AACF;AAKO,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAK5C,YACE,QACA,cACA,oBACA,iBAA0B,OAC1B;AACA,UAAM,wBAAwB,MAAM,EAAE;AACtC,SAAK,OAAO;AACZ,SAAK,eAAe;AACpB,SAAK,qBAAqB;AAC1B,SAAK,iBAAiB;AAAA,EACxB;AACF;","names":["match"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/ids.ts","../src/logger.ts","../src/redaction.ts"],"sourcesContent":["/**\n * G8R Agent Shield SDK\n *\n * Lightweight TypeScript client that wraps LLM calls with policy enforcement.\n * Automatically intercepts prompts, applies best-effort local-first redaction,\n * checks them against the G8R policy engine, and logs all activity to the\n * Agent Shield Console.\n *\n * Usage:\n * import { AgentShield, tenantId } from '@g8r-security/agent-shield-sdk';\n *\n * const shield = new AgentShield({\n * consoleUrl: 'https://shield.yourcompany.com',\n * apiKey: 'sk-shield-...',\n * tenantId: tenantId('acme-corp'),\n * department: 'Finance',\n * userId: 'usr_FIN_042',\n * aiModel: 'GPT-4o',\n * });\n *\n * // Wrap any LLM call — the factory function is only invoked if the policy allows it\n * const result = await shield.wrap(\n * () => openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Summarize Q1 earnings' }],\n * }),\n * 'Summarize Q1 earnings'\n * );\n */\n\nimport { match } from 'ts-pattern';\nimport { newRequestId, type RequestId, type TenantId } from './ids';\nimport { log } from './logger';\nimport { redactSensitiveData } from './redaction';\n\n// ── Public API re-exports ────────────────────────────────────────────────────\n// Consumers need `tenantId()` to construct the branded TenantId required by\n// ShieldConfig, and `redactSensitiveData` is documented as a public helper.\nexport { tenantId, newRequestId } from './ids';\nexport type { TenantId, RequestId } from './ids';\nexport { redactSensitiveData } from './redaction';\nexport type { RedactionResult } from './redaction';\n\nexport interface ShieldConfig {\n /** URL of the G8R Agent Shield Console */\n consoleUrl: string;\n /** API key for authentication */\n apiKey: string;\n /** Tenant this SDK instance operates on behalf of. Required. */\n tenantId: TenantId;\n /** Department of the calling user */\n department: string;\n /** User identifier */\n userId: string;\n /** AI model being called */\n aiModel: string;\n /** Optional: agent identifier (defaults to \"sdk-client\") */\n agentId?: string;\n /** Optional: employee display name (defaults to userId) */\n employeeName?: string;\n}\n\nexport interface PolicyCheckResult {\n decision: 'allowed' | 'blocked' | 'escalated';\n reason: string;\n violatedRule: string | null;\n requiresApproval: boolean;\n /**\n * Set to true when a kill-switch rule fires (e.g. unauthorized partner data\n * access). Consumers should tear down the agent session in response.\n */\n sessionRevoked?: boolean;\n complianceMappings: Array<{\n regulation: string;\n controlId: string;\n controlName: string;\n description: string;\n }>;\n /**\n * Tokens that were redacted from the prompt before it reached the gateway.\n * Undefined when no tokens were redacted (clean prompt).\n * Populated by the local-first redaction layer.\n */\n redactedTokens?: string[];\n}\n\nexport interface ShieldLogEntry {\n id: string;\n decision: string;\n timestamp: string;\n}\n\nexport class AgentShield {\n private config: ShieldConfig;\n\n constructor(config: ShieldConfig) {\n this.config = config;\n }\n\n /**\n * Check a prompt against the policy engine before sending to the LLM.\n *\n * Applies best-effort local-first redaction before sending to the gateway, so\n * recognized signing keys, custodial IDs, common PII, and high-entropy secrets\n * are stripped before the prompt leaves the process. Redaction is one layer of\n * defense, not a guarantee that every secret is caught (see redaction.ts).\n *\n * Returns the policy decision without executing the LLM call.\n */\n async check(\n prompt: string,\n requestId: RequestId = newRequestId()\n ): Promise<PolicyCheckResult> {\n // Step 1: Local-first redaction — strip recognized secrets and PII\n // before the prompt reaches the remote gateway.\n const { redacted, tokensReplaced } = redactSensitiveData(prompt);\n\n // `requestId` is generated per-call by default, but `wrap()` passes its\n // own value so /check and /log share a single correlation id end-to-end\n // (C2). Build a scoped logger bound to tenantId + requestId so any\n // failure path emits lines with that context automatically.\n const scopedLog = log.child({\n tenant_id: this.config.tenantId,\n request_id: requestId,\n });\n\n const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/check`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({\n input: redacted, // send the redacted version — never the raw prompt\n tenantId: this.config.tenantId,\n requestId,\n userId: this.config.userId,\n department: this.config.department,\n aiModel: this.config.aiModel,\n agentId: this.config.agentId ?? 'sdk-client',\n }),\n });\n\n if (!res.ok) {\n scopedLog.error('Shield policy check failed', { status: res.status });\n throw new Error(`Shield policy check failed: ${res.status}`);\n }\n\n const result = await res.json();\n return {\n ...result,\n ...(tokensReplaced.length > 0 ? { redactedTokens: tokensReplaced } : {}),\n };\n }\n\n /**\n * Wrap an LLM call with policy enforcement.\n *\n * The `llmCallFactory` is a function that creates the LLM promise.\n * It is only invoked if the policy engine allows the action.\n * This prevents the LLM call from executing before the policy check completes.\n *\n * @param llmCallFactory - A function that returns the LLM call promise\n * @param prompt - The prompt text to evaluate against the policy engine\n * @returns The result of the LLM call if allowed\n * @throws ShieldBlockedError if the policy engine blocks the action\n *\n * @example\n * const result = await shield.wrap(\n * () => openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: prompt }],\n * }),\n * prompt\n * );\n */\n async wrap<T>(llmCallFactory: () => Promise<T>, prompt: string): Promise<T> {\n // Generate a single requestId for this wrap() invocation and thread it\n // through both /check and /log so the two server-side log lines can be\n // joined end-to-end (C2). Without this, each call would mint its own id\n // and the audit trail would lose the policy→action linkage.\n const requestId = newRequestId();\n\n // Step 1: Check the prompt against the policy engine (includes redaction)\n const policyResult = await this.check(prompt, requestId);\n\n // Step 2: Log the attempt regardless of decision. log() redacts before\n // transmitting, so the audit-log path never leaks raw secrets either.\n await this.log(prompt, policyResult, requestId);\n\n // Step 3: Enforce the decision. Exhaustive match — adding a fourth\n // decision value will fail TypeScript compilation here until handled.\n match(policyResult.decision)\n .with('blocked', () => {\n throw new ShieldBlockedError(\n policyResult.reason,\n policyResult.violatedRule,\n policyResult.complianceMappings,\n policyResult.sessionRevoked ?? false\n );\n })\n .with('escalated', () => {\n // In production, this would await human approval via webhook\n log.warn('Action escalated for human review', {\n reason: policyResult.reason,\n });\n })\n .with('allowed', () => {\n /* fall through to invoke the LLM */\n })\n .exhaustive();\n\n // Step 4: Only now invoke the LLM call — after policy check passed\n return llmCallFactory();\n }\n\n /**\n * Log an interaction to the Agent Shield Console.\n *\n * Redacts the prompt before transmitting: the audit trail must not store or\n * carry raw secrets/PII, and this is an egress point just like /check.\n */\n private async log(\n input: string,\n result: PolicyCheckResult,\n requestId: RequestId = newRequestId()\n ): Promise<ShieldLogEntry> {\n const scopedLog = log.child({\n tenant_id: this.config.tenantId,\n request_id: requestId,\n });\n\n // Redact at the egress boundary — never send the raw prompt to /log.\n const { redacted } = redactSensitiveData(input);\n\n const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/log`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({\n input: redacted,\n tenantId: this.config.tenantId,\n requestId,\n userId: this.config.userId,\n department: this.config.department,\n aiModel: this.config.aiModel,\n agentId: this.config.agentId ?? 'sdk-client',\n employeeName: this.config.employeeName ?? this.config.userId,\n decision: result.decision,\n reason: result.reason,\n violatedRule: result.violatedRule,\n requiresApproval: result.requiresApproval,\n complianceMappings: result.complianceMappings,\n }),\n });\n\n if (!res.ok) {\n scopedLog.error('Failed to log interaction', { status: res.status });\n }\n\n return res.json();\n }\n}\n\n/**\n * Error thrown when the policy engine blocks an LLM call.\n */\nexport class ShieldBlockedError extends Error {\n public readonly violatedRule: string | null;\n public readonly complianceMappings: PolicyCheckResult['complianceMappings'];\n public readonly sessionRevoked: boolean;\n\n constructor(\n reason: string,\n violatedRule: string | null,\n complianceMappings: PolicyCheckResult['complianceMappings'],\n sessionRevoked: boolean = false\n ) {\n super(`[G8R Shield BLOCKED] ${reason}`);\n this.name = 'ShieldBlockedError';\n this.violatedRule = violatedRule;\n this.complianceMappings = complianceMappings;\n this.sessionRevoked = sessionRevoked;\n }\n}\n","/**\n * Branded id types + constructors used by the SDK.\n *\n * Self-contained in the SDK so the published `@g8r-security/agent-shield-sdk`\n * package has no dependency on internal packages. Only the id types/helpers the\n * SDK actually uses are included here.\n */\n\n/**\n * Identifies a single tenant in the multi-tenant governance plane.\n * Branded so a raw string can't be passed where a TenantId is expected.\n */\nexport type TenantId = string & { readonly __brand: 'TenantId' };\n\n/** Per-request correlation ID. Generated client-side by the SDK. */\nexport type RequestId = string & { readonly __brand: 'RequestId' };\n\nconst TENANT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;\n\n/**\n * Cast a string to TenantId. Use at boundaries (config, env, request body).\n * Enforces the charset / length contract: 1-64 chars of `[a-z0-9-]`,\n * starting with `[a-z0-9]` — catches programmatic misuse (config typos,\n * hard-coded slugs that drift).\n */\nexport function tenantId(s: string): TenantId {\n if (!s) throw new Error('tenantId cannot be empty');\n if (!TENANT_ID_PATTERN.test(s)) {\n throw new Error(\n `tenantId must be 1-64 chars, [a-z0-9-], starting with [a-z0-9] (got ${JSON.stringify(s)})`\n );\n }\n return s as TenantId;\n}\n\n/** Generate a fresh request ID. Prefers crypto.randomUUID where available. */\nexport function newRequestId(): RequestId {\n // crypto.randomUUID is available in Node 19+ and all modern browsers.\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID() as RequestId;\n }\n // Fallback: timestamp + Math.random (best-effort, not crypto-strong).\n return `req-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}` as RequestId;\n}\n","/**\n * G8R structured JSON logger.\n *\n * Tiny, dependency-free logger that emits one JSON object per line. Designed\n * for governance contexts where each log line must carry `tenant_id` and\n * `request_id` so downstream pipelines can route audit trails per-tenant.\n *\n * Self-contained in the SDK so the published `@g8r-security/agent-shield-sdk`\n * package has no dependency on internal packages.\n *\n * Use the default `log` singleton for app-level chatter; call `createLogger`\n * (or `.child()`) when you need per-request bindings injected.\n */\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nexport interface LogContext {\n tenant_id?: string;\n request_id?: string;\n [key: string]: unknown;\n}\n\nexport interface Logger {\n debug(msg: string, ctx?: LogContext): void;\n info(msg: string, ctx?: LogContext): void;\n warn(msg: string, ctx?: LogContext): void;\n error(msg: string, ctx?: LogContext): void;\n child(ctx: LogContext): Logger;\n}\n\nexport interface LoggerOptions {\n /** Defaults to 'info'. Anything below is dropped. */\n level?: LogLevel;\n /** Defaults to console.log. Override for testing or transport injection. */\n sink?: (line: string) => void;\n /** Context merged into every log line. */\n bindings?: LogContext;\n}\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n};\n\nexport function createLogger(opts: LoggerOptions = {}): Logger {\n const minLevel = LEVEL_ORDER[opts.level ?? 'info'];\n const sink = opts.sink ?? ((line: string) => console.log(line));\n const bindings = opts.bindings ?? {};\n\n function emit(level: LogLevel, msg: string, ctx?: LogContext): void {\n if (LEVEL_ORDER[level] < minLevel) return;\n const payload = {\n level,\n msg,\n ts: new Date().toISOString(),\n ...bindings,\n ...ctx,\n };\n sink(JSON.stringify(payload));\n }\n\n return {\n debug: (msg, ctx) => emit('debug', msg, ctx),\n info: (msg, ctx) => emit('info', msg, ctx),\n warn: (msg, ctx) => emit('warn', msg, ctx),\n error: (msg, ctx) => emit('error', msg, ctx),\n child: (extra) => createLogger({ ...opts, bindings: { ...bindings, ...extra } }),\n };\n}\n\n/** Default singleton — convenient for apps that don't need DI. */\nexport const log = createLogger();\n","/**\n * Local-First Sensitive Data Redaction\n *\n * Best-effort redaction of common secret and PII formats BEFORE prompts reach\n * the policy gateway. Covers cryptographic key formats, custodial identifiers,\n * high-entropy strings, and common PII (card numbers validated via Luhn, US\n * SSNs, email addresses, and phone numbers).\n *\n * IMPORTANT — this is a defense-in-depth layer, NOT a guarantee of completeness.\n * Pattern- and entropy-based redaction cannot catch every secret or PII shape\n * (e.g. unstructured PII, names, novel token formats, or values below the\n * entropy threshold). Do not rely on it as a sole control; keep downstream\n * safeguards and human review in place.\n *\n * It helps *support* (does not by itself satisfy) controls such as GDPR Art. 32\n * and PCI-DSS PAN-handling by reducing sensitive-data exposure to the gateway.\n */\n\nexport interface RedactionResult {\n /** The input with all sensitive tokens replaced by placeholder strings. */\n redacted: string;\n /** The original sensitive token strings that were replaced. */\n tokensReplaced: string[];\n}\n\n/**\n * Shannon entropy: calculates bits per character.\n * High entropy (> 4.5) indicates likely cryptographic material.\n */\nfunction shannonEntropy(str: string): number {\n const freq = new Map<string, number>();\n for (const ch of str) {\n freq.set(ch, (freq.get(ch) ?? 0) + 1);\n }\n let entropy = 0;\n const len = str.length;\n for (const count of freq.values()) {\n const p = count / len;\n entropy -= p * Math.log2(p);\n }\n return entropy;\n}\n\n// ── Signing Key Patterns ─────────────────────────────────────────────────────\n\n/** BIP-32 extended public/private keys: xpub, xprv, ypub, zpub, zprv, etc. */\nconst BIP32_PATTERN = /\\b[xyz](?:pub|prv)[a-zA-Z0-9]{99,111}\\b/g;\n\n/**\n * WIF (Wallet Import Format) private keys.\n * Base58Check encoded, starts with 5 (uncompressed) or K/L (compressed), 51–52 chars.\n */\nconst WIF_PATTERN = /\\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\\b/g;\n\n/**\n * Raw hex 256-bit keys — exactly 64 hex characters, optionally 0x-prefixed.\n * Matches Ethereum private keys, secp256k1 scalars, etc.\n */\nconst HEX_KEY_PATTERN = /\\b(?:0x)?[0-9a-fA-F]{64}\\b/g;\n\n/** PEM-encoded private or public key blocks (multi-line). */\nconst PEM_PATTERN =\n /-----BEGIN (?:RSA |EC |OPENSSH )?(?:PRIVATE|PUBLIC) KEY-----[\\s\\S]*?-----END (?:RSA |EC |OPENSSH )?(?:PRIVATE|PUBLIC) KEY-----/g;\n\n// ── Custodial ID Patterns ─────────────────────────────────────────────────────\n\n/** Custodial-id format: `custodial-id:abc123xyz` */\nconst CUSTODIAL_ID_PATTERN = /\\bcustodial-id:[A-Za-z0-9_-]+\\b/g;\n\n/** Short custodial references: `cust-98765` */\nconst CUST_PATTERN = /\\bcust-\\d+\\b/gi;\n\n/** Wallet identifiers: `wallet-id:wlt-abc999` */\nconst WALLET_ID_PATTERN = /\\bwallet-id:[A-Za-z0-9_-]+\\b/g;\n\n/** Vault identifiers: `vault-id:v-secure-001` */\nconst VAULT_ID_PATTERN = /\\bvault-id:[A-Za-z0-9_-]+\\b/g;\n\n// ── PII Patterns (best-effort) ────────────────────────────────────────────────\n\n/** Email addresses. */\nconst EMAIL_PATTERN = /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/g;\n\n/** US Social Security Numbers in dashed or spaced form, e.g. `123-45-6789`. */\nconst SSN_PATTERN = /\\b\\d{3}[ -]\\d{2}[ -]\\d{4}\\b/g;\n\n/**\n * Phone numbers with explicit separators (avoids matching arbitrary digit runs).\n * Optional country code, then 3-3-4 with space/dot/hyphen between groups.\n */\nconst PHONE_PATTERN = /\\b(?:\\+?\\d{1,3}[ .-]?)?\\(?\\d{3}\\)?[ .-]\\d{3}[ .-]\\d{4}\\b/g;\n\n/**\n * Candidate card-number runs: 13–19 digits with optional single space/hyphen\n * separators. Validated with Luhn before redacting to avoid false positives on\n * arbitrary long numbers (order/invoice IDs, etc.).\n */\nconst CARD_CANDIDATE_PATTERN = /\\b\\d(?:[ -]?\\d){12,18}\\b/g;\n\n/**\n * Luhn checksum — used to gate card-number redaction so we only mask digit runs\n * that actually pass the check digit, not every long number.\n */\nfunction luhnValid(digits: string): boolean {\n let sum = 0;\n let double = false;\n for (let i = digits.length - 1; i >= 0; i--) {\n let d = digits.charCodeAt(i) - 48;\n if (double) {\n d *= 2;\n if (d > 9) d -= 9;\n }\n sum += d;\n double = !double;\n }\n return sum % 10 === 0;\n}\n\n// ── Entropy Detection Constants ───────────────────────────────────────────────\n\n/** Minimum Shannon entropy (bits/char) to flag a token as likely cryptographic material. */\nconst ENTROPY_THRESHOLD = 4.5;\n\n/** Minimum token length to apply entropy analysis (shorter strings are too ambiguous). */\nconst ENTROPY_MIN_LENGTH = 32;\n\n/**\n * Find tokens in the string that exceed the entropy threshold.\n * Splits on whitespace and common delimiters to isolate candidates.\n */\nfunction extractHighEntropyTokens(input: string): string[] {\n const candidates = input.split(/[\\s,;:\"'`(){}[\\]<>]+/).filter(Boolean);\n return candidates.filter(\n (token) => token.length >= ENTROPY_MIN_LENGTH && shannonEntropy(token) >= ENTROPY_THRESHOLD\n );\n}\n\n/**\n * Redact sensitive data from a prompt string before it reaches the gateway.\n *\n * Processing order (important — PEM first to avoid splitting on inner patterns):\n * 1. PEM private/public key blocks\n * 2. BIP-32 extended keys\n * 3. WIF private keys\n * 4. Raw hex 256-bit keys\n * 5. Custodial IDs (all four variants)\n * 6. PII — card numbers (Luhn-validated), SSNs, emails, phone numbers\n * 7. High-entropy string catch-all\n */\nexport function redactSensitiveData(input: string): RedactionResult {\n const tokensReplaced: string[] = [];\n let redacted = input;\n\n function replaceAll(pattern: RegExp, label: string): void {\n redacted = redacted.replace(pattern, (match) => {\n tokensReplaced.push(match);\n return `[REDACTED:${label}]`;\n });\n }\n\n replaceAll(PEM_PATTERN, 'PEM_KEY');\n replaceAll(BIP32_PATTERN, 'BIP32_KEY');\n replaceAll(WIF_PATTERN, 'WIF_KEY');\n replaceAll(HEX_KEY_PATTERN, 'HEX_KEY');\n replaceAll(CUSTODIAL_ID_PATTERN, 'CUSTODIAL_ID');\n replaceAll(CUST_PATTERN, 'CUST_ID');\n replaceAll(WALLET_ID_PATTERN, 'WALLET_ID');\n replaceAll(VAULT_ID_PATTERN, 'VAULT_ID');\n\n // PII (best-effort). Card numbers first, gated by Luhn so we don't mask every\n // long digit run; then structured SSN / email / phone formats.\n redacted = redacted.replace(CARD_CANDIDATE_PATTERN, (match) => {\n const digits = match.replace(/\\D/g, '');\n if (digits.length >= 13 && digits.length <= 19 && luhnValid(digits)) {\n tokensReplaced.push(match);\n return '[REDACTED:CARD]';\n }\n return match;\n });\n replaceAll(SSN_PATTERN, 'SSN');\n replaceAll(EMAIL_PATTERN, 'EMAIL');\n replaceAll(PHONE_PATTERN, 'PHONE');\n\n // High-entropy catch-all — runs on the already-redacted string to avoid double-replacing.\n const highEntropyTokens = extractHighEntropyTokens(redacted);\n for (const token of highEntropyTokens) {\n if (!redacted.includes(token)) continue;\n tokensReplaced.push(token);\n redacted = redacted.split(token).join('[REDACTED:HIGH_ENTROPY]');\n }\n\n return { redacted, tokensReplaced };\n}\n"],"mappings":"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","names":["match"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@g8r-security/agent-shield-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "TypeScript client for G8R Agent Shield — wrap LLM and agent calls with policy enforcement, local-first redaction, and audit logging.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {