@g8r-security/agent-shield-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # @g8r-security/agent-shield-sdk
2
+
3
+ TypeScript client SDK for integrating AI agents with G8R policy enforcement. Includes a **local VPC redaction layer** that strips sensitive data before it ever leaves your network.
4
+
5
+ Part of the [G8R Agent Shield monorepo](../../README.md).
6
+
7
+ ## Overview
8
+
9
+ This is a **TypeScript source package** — no compile step. Consumers reference `./src/index.ts` directly via `transpilePackages` (Next.js) or `resolve.alias` (Vite).
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { AgentShield } from '@g8r-security/agent-shield-sdk';
15
+
16
+ const shield = new AgentShield({
17
+ consoleUrl: 'https://shield.yourcompany.com',
18
+ apiKey: 'sk-shield-...',
19
+ agentId: 'enterprise-assistant',
20
+ department: 'Finance',
21
+ userId: 'usr_FIN_042',
22
+ aiModel: 'GPT-4o',
23
+ });
24
+
25
+ // Factory pattern — LLM is only called if the policy check passes
26
+ const result = await shield.wrap(
27
+ () => openai.chat.completions.create({ model: 'gpt-4o', messages }),
28
+ userPrompt
29
+ );
30
+ ```
31
+
32
+ ## API
33
+
34
+ ### `shield.wrap(factory, prompt)`
35
+
36
+ The primary integration point. Runs the full pipeline:
37
+
38
+ 1. **Redact** — `redactSensitiveData(prompt)` strips secrets locally
39
+ 2. **Check** — POST redacted prompt to `/api/sdk/v1/check` (policy evaluation)
40
+ 3. **Log** — POST audit entry to `/api/sdk/v1/log`
41
+ 4. **Invoke** — call `factory()` only if decision is `allowed` or `escalated`
42
+
43
+ If blocked, throws `ShieldBlockedError` — the factory is **never called**.
44
+
45
+ ```typescript
46
+ try {
47
+ const result = await shield.wrap(() => llmCall(), prompt);
48
+ console.log(result.llmResult); // LLM response
49
+ console.log(result.redactedTokens); // Tokens stripped from the prompt
50
+ } catch (err) {
51
+ if (err instanceof ShieldBlockedError) {
52
+ console.log(err.violatedRule); // e.g. 'PII Detection'
53
+ console.log(err.complianceMappings); // GDPR Art. 32, etc.
54
+ }
55
+ }
56
+ ```
57
+
58
+ ### `shield.check(prompt)`
59
+
60
+ Policy check only — no LLM call.
61
+
62
+ ```typescript
63
+ const result = await shield.check(prompt);
64
+ // result.decision → 'allowed' | 'blocked' | 'escalated'
65
+ // result.redactedTokens → tokens stripped from the prompt before sending
66
+ ```
67
+
68
+ ### `redactSensitiveData(input)`
69
+
70
+ Standalone redaction — can be used independently of the shield client.
71
+
72
+ ```typescript
73
+ import { redactSensitiveData } from '@g8r-security/agent-shield-sdk';
74
+
75
+ const { redacted, tokensReplaced } = redactSensitiveData(input);
76
+ ```
77
+
78
+ ## VPC Redaction Layer
79
+
80
+ Sensitive data is detected and replaced **locally** before leaving the VPC. The gateway never sees raw secrets.
81
+
82
+ ### Detection Patterns
83
+
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 |
95
+
96
+ ### Shannon Entropy Detection
97
+
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.
99
+
100
+ ```
101
+ H = -Σ p(c) × log₂(p(c)) for each unique character c
102
+ ```
103
+
104
+ Low-entropy strings (e.g. `"aaaaaaaaaaaaa"`, entropy ≈ 0) are not flagged.
105
+
106
+ ## `PolicyCheckResult`
107
+
108
+ ```typescript
109
+ interface PolicyCheckResult {
110
+ decision: 'allowed' | 'blocked' | 'escalated';
111
+ reason: string;
112
+ violatedRule: string | null;
113
+ requiresApproval: boolean;
114
+ complianceMappings: ComplianceMapping[];
115
+ sessionRevoked?: boolean;
116
+ redactedTokens?: string[]; // Tokens stripped by VPC masking layer
117
+ }
118
+ ```
119
+
120
+ ## `ShieldBlockedError`
121
+
122
+ Thrown by `shield.wrap()` when `decision === 'blocked'`.
123
+
124
+ ```typescript
125
+ class ShieldBlockedError extends Error {
126
+ decision: 'blocked';
127
+ reason: string;
128
+ violatedRule: string | null;
129
+ complianceMappings: ComplianceMapping[];
130
+ }
131
+ ```
132
+
133
+ ## Source Layout
134
+
135
+ ```
136
+ packages/sdk/
137
+ ├── src/
138
+ │ ├── index.ts # AgentShield class, ShieldBlockedError, PolicyCheckResult
139
+ │ └── redaction.ts # redactSensitiveData() — VPC masking layer
140
+ ├── __tests__/
141
+ │ ├── sdk.test.ts # 43 tests — SDK client behavior + redaction integration
142
+ │ └── redaction.test.ts # 43 tests — all patterns + entropy detection
143
+ ├── jest.config.ts
144
+ ├── package.json
145
+ └── tsconfig.json
146
+ ```
147
+
148
+ ## Development
149
+
150
+ ```bash
151
+ cd packages/sdk
152
+
153
+ npm test # Jest (43 tests)
154
+ npm run test:coverage # With coverage (97%+ statements)
155
+ npm run typecheck # tsc --noEmit
156
+ ```
157
+
158
+ ## Coverage
159
+
160
+ Thresholds in `jest.config.ts`: 85% statements, 75% branches, 85% functions, 85% lines.
161
+
162
+ ## Compliance
163
+
164
+ | Regulation | Controls covered |
165
+ |---|---|
166
+ | GDPR | Art. 32 — appropriate technical measures for data protection |
167
+ | PCI-DSS v4.0 | 3.4 — cryptographic key protection |
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Branded id types + constructors used by the SDK.
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.
8
+ */
9
+ /**
10
+ * Identifies a single tenant in the multi-tenant governance plane.
11
+ * Branded so a raw string can't be passed where a TenantId is expected.
12
+ */
13
+ type TenantId = string & {
14
+ readonly __brand: 'TenantId';
15
+ };
16
+ /** Per-request correlation ID. Generated client-side by the SDK. */
17
+ type RequestId = string & {
18
+ readonly __brand: 'RequestId';
19
+ };
20
+
21
+ /**
22
+ * G8R Agent Shield SDK
23
+ *
24
+ * Lightweight TypeScript client that wraps LLM calls with policy enforcement.
25
+ * Automatically intercepts prompts, applies local-first VPC redaction (BitGo),
26
+ * checks them against the G8R policy engine, and logs all activity to the
27
+ * Agent Shield Console.
28
+ *
29
+ * Usage:
30
+ * import { AgentShield } from '@g8r-security/agent-shield-sdk';
31
+ *
32
+ * const shield = new AgentShield({
33
+ * consoleUrl: 'https://shield.yourcompany.com',
34
+ * apiKey: 'sk-shield-...',
35
+ * department: 'Finance',
36
+ * userId: 'usr_FIN_042',
37
+ * aiModel: 'GPT-4o',
38
+ * });
39
+ *
40
+ * // Wrap any LLM call — the factory function is only invoked if the policy allows it
41
+ * const result = await shield.wrap(
42
+ * () => openai.chat.completions.create({
43
+ * model: 'gpt-4o',
44
+ * messages: [{ role: 'user', content: 'Summarize Q1 earnings' }],
45
+ * }),
46
+ * 'Summarize Q1 earnings'
47
+ * );
48
+ */
49
+
50
+ interface ShieldConfig {
51
+ /** URL of the G8R Agent Shield Console */
52
+ consoleUrl: string;
53
+ /** API key for authentication */
54
+ apiKey: string;
55
+ /** Tenant this SDK instance operates on behalf of. Required. */
56
+ tenantId: TenantId;
57
+ /** Department of the calling user */
58
+ department: string;
59
+ /** User identifier */
60
+ userId: string;
61
+ /** AI model being called */
62
+ aiModel: string;
63
+ /** Optional: agent identifier (defaults to "sdk-client") */
64
+ agentId?: string;
65
+ /** Optional: employee display name (defaults to userId) */
66
+ employeeName?: string;
67
+ }
68
+ interface PolicyCheckResult {
69
+ decision: 'allowed' | 'blocked' | 'escalated';
70
+ reason: string;
71
+ violatedRule: string | null;
72
+ requiresApproval: boolean;
73
+ /**
74
+ * Set to true when a kill-switch rule fires (e.g. unauthorized partner data
75
+ * access). Consumers should tear down the agent session in response.
76
+ */
77
+ sessionRevoked?: boolean;
78
+ complianceMappings: Array<{
79
+ regulation: string;
80
+ controlId: string;
81
+ controlName: string;
82
+ description: string;
83
+ }>;
84
+ /**
85
+ * Tokens that were redacted from the prompt before it reached the gateway.
86
+ * Undefined when no tokens were redacted (clean prompt).
87
+ * Populated by the BitGo VPC local-first redaction layer.
88
+ */
89
+ redactedTokens?: string[];
90
+ }
91
+ interface ShieldLogEntry {
92
+ id: string;
93
+ decision: string;
94
+ timestamp: string;
95
+ }
96
+ declare class AgentShield {
97
+ private config;
98
+ constructor(config: ShieldConfig);
99
+ /**
100
+ * Check a prompt against the policy engine before sending to the LLM.
101
+ *
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.
105
+ *
106
+ * Returns the policy decision without executing the LLM call.
107
+ */
108
+ check(prompt: string, requestId?: RequestId): Promise<PolicyCheckResult>;
109
+ /**
110
+ * Wrap an LLM call with policy enforcement.
111
+ *
112
+ * The `llmCallFactory` is a function that creates the LLM promise.
113
+ * It is only invoked if the policy engine allows the action.
114
+ * This prevents the LLM call from executing before the policy check completes.
115
+ *
116
+ * @param llmCallFactory - A function that returns the LLM call promise
117
+ * @param prompt - The prompt text to evaluate against the policy engine
118
+ * @returns The result of the LLM call if allowed
119
+ * @throws ShieldBlockedError if the policy engine blocks the action
120
+ *
121
+ * @example
122
+ * const result = await shield.wrap(
123
+ * () => openai.chat.completions.create({
124
+ * model: 'gpt-4o',
125
+ * messages: [{ role: 'user', content: prompt }],
126
+ * }),
127
+ * prompt
128
+ * );
129
+ */
130
+ wrap<T>(llmCallFactory: () => Promise<T>, prompt: string): Promise<T>;
131
+ /**
132
+ * Log an interaction to the Agent Shield Console.
133
+ */
134
+ private log;
135
+ }
136
+ /**
137
+ * Error thrown when the policy engine blocks an LLM call.
138
+ */
139
+ declare class ShieldBlockedError extends Error {
140
+ readonly violatedRule: string | null;
141
+ readonly complianceMappings: PolicyCheckResult['complianceMappings'];
142
+ readonly sessionRevoked: boolean;
143
+ constructor(reason: string, violatedRule: string | null, complianceMappings: PolicyCheckResult['complianceMappings'], sessionRevoked?: boolean);
144
+ }
145
+
146
+ export { AgentShield, type PolicyCheckResult, ShieldBlockedError, type ShieldConfig, type ShieldLogEntry };
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Branded id types + constructors used by the SDK.
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.
8
+ */
9
+ /**
10
+ * Identifies a single tenant in the multi-tenant governance plane.
11
+ * Branded so a raw string can't be passed where a TenantId is expected.
12
+ */
13
+ type TenantId = string & {
14
+ readonly __brand: 'TenantId';
15
+ };
16
+ /** Per-request correlation ID. Generated client-side by the SDK. */
17
+ type RequestId = string & {
18
+ readonly __brand: 'RequestId';
19
+ };
20
+
21
+ /**
22
+ * G8R Agent Shield SDK
23
+ *
24
+ * Lightweight TypeScript client that wraps LLM calls with policy enforcement.
25
+ * Automatically intercepts prompts, applies local-first VPC redaction (BitGo),
26
+ * checks them against the G8R policy engine, and logs all activity to the
27
+ * Agent Shield Console.
28
+ *
29
+ * Usage:
30
+ * import { AgentShield } from '@g8r-security/agent-shield-sdk';
31
+ *
32
+ * const shield = new AgentShield({
33
+ * consoleUrl: 'https://shield.yourcompany.com',
34
+ * apiKey: 'sk-shield-...',
35
+ * department: 'Finance',
36
+ * userId: 'usr_FIN_042',
37
+ * aiModel: 'GPT-4o',
38
+ * });
39
+ *
40
+ * // Wrap any LLM call — the factory function is only invoked if the policy allows it
41
+ * const result = await shield.wrap(
42
+ * () => openai.chat.completions.create({
43
+ * model: 'gpt-4o',
44
+ * messages: [{ role: 'user', content: 'Summarize Q1 earnings' }],
45
+ * }),
46
+ * 'Summarize Q1 earnings'
47
+ * );
48
+ */
49
+
50
+ interface ShieldConfig {
51
+ /** URL of the G8R Agent Shield Console */
52
+ consoleUrl: string;
53
+ /** API key for authentication */
54
+ apiKey: string;
55
+ /** Tenant this SDK instance operates on behalf of. Required. */
56
+ tenantId: TenantId;
57
+ /** Department of the calling user */
58
+ department: string;
59
+ /** User identifier */
60
+ userId: string;
61
+ /** AI model being called */
62
+ aiModel: string;
63
+ /** Optional: agent identifier (defaults to "sdk-client") */
64
+ agentId?: string;
65
+ /** Optional: employee display name (defaults to userId) */
66
+ employeeName?: string;
67
+ }
68
+ interface PolicyCheckResult {
69
+ decision: 'allowed' | 'blocked' | 'escalated';
70
+ reason: string;
71
+ violatedRule: string | null;
72
+ requiresApproval: boolean;
73
+ /**
74
+ * Set to true when a kill-switch rule fires (e.g. unauthorized partner data
75
+ * access). Consumers should tear down the agent session in response.
76
+ */
77
+ sessionRevoked?: boolean;
78
+ complianceMappings: Array<{
79
+ regulation: string;
80
+ controlId: string;
81
+ controlName: string;
82
+ description: string;
83
+ }>;
84
+ /**
85
+ * Tokens that were redacted from the prompt before it reached the gateway.
86
+ * Undefined when no tokens were redacted (clean prompt).
87
+ * Populated by the BitGo VPC local-first redaction layer.
88
+ */
89
+ redactedTokens?: string[];
90
+ }
91
+ interface ShieldLogEntry {
92
+ id: string;
93
+ decision: string;
94
+ timestamp: string;
95
+ }
96
+ declare class AgentShield {
97
+ private config;
98
+ constructor(config: ShieldConfig);
99
+ /**
100
+ * Check a prompt against the policy engine before sending to the LLM.
101
+ *
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.
105
+ *
106
+ * Returns the policy decision without executing the LLM call.
107
+ */
108
+ check(prompt: string, requestId?: RequestId): Promise<PolicyCheckResult>;
109
+ /**
110
+ * Wrap an LLM call with policy enforcement.
111
+ *
112
+ * The `llmCallFactory` is a function that creates the LLM promise.
113
+ * It is only invoked if the policy engine allows the action.
114
+ * This prevents the LLM call from executing before the policy check completes.
115
+ *
116
+ * @param llmCallFactory - A function that returns the LLM call promise
117
+ * @param prompt - The prompt text to evaluate against the policy engine
118
+ * @returns The result of the LLM call if allowed
119
+ * @throws ShieldBlockedError if the policy engine blocks the action
120
+ *
121
+ * @example
122
+ * const result = await shield.wrap(
123
+ * () => openai.chat.completions.create({
124
+ * model: 'gpt-4o',
125
+ * messages: [{ role: 'user', content: prompt }],
126
+ * }),
127
+ * prompt
128
+ * );
129
+ */
130
+ wrap<T>(llmCallFactory: () => Promise<T>, prompt: string): Promise<T>;
131
+ /**
132
+ * Log an interaction to the Agent Shield Console.
133
+ */
134
+ private log;
135
+ }
136
+ /**
137
+ * Error thrown when the policy engine blocks an LLM call.
138
+ */
139
+ declare class ShieldBlockedError extends Error {
140
+ readonly violatedRule: string | null;
141
+ readonly complianceMappings: PolicyCheckResult['complianceMappings'];
142
+ readonly sessionRevoked: boolean;
143
+ constructor(reason: string, violatedRule: string | null, complianceMappings: PolicyCheckResult['complianceMappings'], sessionRevoked?: boolean);
144
+ }
145
+
146
+ export { AgentShield, type PolicyCheckResult, ShieldBlockedError, type ShieldConfig, type ShieldLogEntry };
package/dist/index.js ADDED
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AgentShield: () => AgentShield,
24
+ ShieldBlockedError: () => ShieldBlockedError
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var import_ts_pattern = require("ts-pattern");
28
+
29
+ // src/ids.ts
30
+ function newRequestId() {
31
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
32
+ return crypto.randomUUID();
33
+ }
34
+ return `req-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
35
+ }
36
+
37
+ // src/logger.ts
38
+ var LEVEL_ORDER = {
39
+ debug: 10,
40
+ info: 20,
41
+ warn: 30,
42
+ error: 40
43
+ };
44
+ function createLogger(opts = {}) {
45
+ const minLevel = LEVEL_ORDER[opts.level ?? "info"];
46
+ const sink = opts.sink ?? ((line) => console.log(line));
47
+ const bindings = opts.bindings ?? {};
48
+ function emit(level, msg, ctx) {
49
+ if (LEVEL_ORDER[level] < minLevel) return;
50
+ const payload = {
51
+ level,
52
+ msg,
53
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
54
+ ...bindings,
55
+ ...ctx
56
+ };
57
+ sink(JSON.stringify(payload));
58
+ }
59
+ return {
60
+ debug: (msg, ctx) => emit("debug", msg, ctx),
61
+ info: (msg, ctx) => emit("info", msg, ctx),
62
+ warn: (msg, ctx) => emit("warn", msg, ctx),
63
+ error: (msg, ctx) => emit("error", msg, ctx),
64
+ child: (extra) => createLogger({ ...opts, bindings: { ...bindings, ...extra } })
65
+ };
66
+ }
67
+ var log = createLogger();
68
+
69
+ // src/redaction.ts
70
+ function shannonEntropy(str) {
71
+ const freq = /* @__PURE__ */ new Map();
72
+ for (const ch of str) {
73
+ freq.set(ch, (freq.get(ch) ?? 0) + 1);
74
+ }
75
+ let entropy = 0;
76
+ const len = str.length;
77
+ for (const count of freq.values()) {
78
+ const p = count / len;
79
+ entropy -= p * Math.log2(p);
80
+ }
81
+ return entropy;
82
+ }
83
+ var BIP32_PATTERN = /\b[xyz](?:pub|prv)[a-zA-Z0-9]{99,111}\b/g;
84
+ var WIF_PATTERN = /\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\b/g;
85
+ var HEX_KEY_PATTERN = /\b(?:0x)?[0-9a-fA-F]{64}\b/g;
86
+ var PEM_PATTERN = /-----BEGIN (?:RSA |EC |OPENSSH )?(?:PRIVATE|PUBLIC) KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?(?:PRIVATE|PUBLIC) KEY-----/g;
87
+ var CUSTODIAL_ID_PATTERN = /\bcustodial-id:[A-Za-z0-9_-]+\b/g;
88
+ var CUST_PATTERN = /\bcust-\d+\b/gi;
89
+ var WALLET_ID_PATTERN = /\bwallet-id:[A-Za-z0-9_-]+\b/g;
90
+ var VAULT_ID_PATTERN = /\bvault-id:[A-Za-z0-9_-]+\b/g;
91
+ var ENTROPY_THRESHOLD = 4.5;
92
+ var ENTROPY_MIN_LENGTH = 32;
93
+ function extractHighEntropyTokens(input) {
94
+ const candidates = input.split(/[\s,;:"'`(){}[\]<>]+/).filter(Boolean);
95
+ return candidates.filter(
96
+ (token) => token.length >= ENTROPY_MIN_LENGTH && shannonEntropy(token) >= ENTROPY_THRESHOLD
97
+ );
98
+ }
99
+ function redactSensitiveData(input) {
100
+ const tokensReplaced = [];
101
+ let redacted = input;
102
+ function replaceAll(pattern, label) {
103
+ redacted = redacted.replace(pattern, (match2) => {
104
+ tokensReplaced.push(match2);
105
+ return `[REDACTED:${label}]`;
106
+ });
107
+ }
108
+ replaceAll(PEM_PATTERN, "PEM_KEY");
109
+ replaceAll(BIP32_PATTERN, "BIP32_KEY");
110
+ replaceAll(WIF_PATTERN, "WIF_KEY");
111
+ replaceAll(HEX_KEY_PATTERN, "HEX_KEY");
112
+ replaceAll(CUSTODIAL_ID_PATTERN, "CUSTODIAL_ID");
113
+ replaceAll(CUST_PATTERN, "CUST_ID");
114
+ replaceAll(WALLET_ID_PATTERN, "WALLET_ID");
115
+ replaceAll(VAULT_ID_PATTERN, "VAULT_ID");
116
+ const highEntropyTokens = extractHighEntropyTokens(redacted);
117
+ for (const token of highEntropyTokens) {
118
+ if (!redacted.includes(token)) continue;
119
+ tokensReplaced.push(token);
120
+ redacted = redacted.split(token).join("[REDACTED:HIGH_ENTROPY]");
121
+ }
122
+ return { redacted, tokensReplaced };
123
+ }
124
+
125
+ // src/index.ts
126
+ var AgentShield = class {
127
+ constructor(config) {
128
+ this.config = config;
129
+ }
130
+ /**
131
+ * Check a prompt against the policy engine before sending to the LLM.
132
+ *
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.
136
+ *
137
+ * Returns the policy decision without executing the LLM call.
138
+ */
139
+ async check(prompt, requestId = newRequestId()) {
140
+ const { redacted, tokensReplaced } = redactSensitiveData(prompt);
141
+ const scopedLog = log.child({
142
+ tenant_id: this.config.tenantId,
143
+ request_id: requestId
144
+ });
145
+ const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/check`, {
146
+ method: "POST",
147
+ headers: {
148
+ "Content-Type": "application/json",
149
+ Authorization: `Bearer ${this.config.apiKey}`
150
+ },
151
+ body: JSON.stringify({
152
+ input: redacted,
153
+ // send the redacted version — never the raw prompt
154
+ tenantId: this.config.tenantId,
155
+ requestId,
156
+ userId: this.config.userId,
157
+ department: this.config.department,
158
+ aiModel: this.config.aiModel,
159
+ agentId: this.config.agentId ?? "sdk-client"
160
+ })
161
+ });
162
+ if (!res.ok) {
163
+ scopedLog.error("Shield policy check failed", { status: res.status });
164
+ throw new Error(`Shield policy check failed: ${res.status}`);
165
+ }
166
+ const result = await res.json();
167
+ return {
168
+ ...result,
169
+ ...tokensReplaced.length > 0 ? { redactedTokens: tokensReplaced } : {}
170
+ };
171
+ }
172
+ /**
173
+ * Wrap an LLM call with policy enforcement.
174
+ *
175
+ * The `llmCallFactory` is a function that creates the LLM promise.
176
+ * It is only invoked if the policy engine allows the action.
177
+ * This prevents the LLM call from executing before the policy check completes.
178
+ *
179
+ * @param llmCallFactory - A function that returns the LLM call promise
180
+ * @param prompt - The prompt text to evaluate against the policy engine
181
+ * @returns The result of the LLM call if allowed
182
+ * @throws ShieldBlockedError if the policy engine blocks the action
183
+ *
184
+ * @example
185
+ * const result = await shield.wrap(
186
+ * () => openai.chat.completions.create({
187
+ * model: 'gpt-4o',
188
+ * messages: [{ role: 'user', content: prompt }],
189
+ * }),
190
+ * prompt
191
+ * );
192
+ */
193
+ async wrap(llmCallFactory, prompt) {
194
+ const requestId = newRequestId();
195
+ const policyResult = await this.check(prompt, requestId);
196
+ await this.log(prompt, policyResult, requestId);
197
+ (0, import_ts_pattern.match)(policyResult.decision).with("blocked", () => {
198
+ throw new ShieldBlockedError(
199
+ policyResult.reason,
200
+ policyResult.violatedRule,
201
+ policyResult.complianceMappings,
202
+ policyResult.sessionRevoked ?? false
203
+ );
204
+ }).with("escalated", () => {
205
+ log.warn("Action escalated for human review", {
206
+ reason: policyResult.reason
207
+ });
208
+ }).with("allowed", () => {
209
+ }).exhaustive();
210
+ return llmCallFactory();
211
+ }
212
+ /**
213
+ * Log an interaction to the Agent Shield Console.
214
+ */
215
+ async log(input, result, requestId = newRequestId()) {
216
+ const scopedLog = log.child({
217
+ tenant_id: this.config.tenantId,
218
+ request_id: requestId
219
+ });
220
+ const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/log`, {
221
+ method: "POST",
222
+ headers: {
223
+ "Content-Type": "application/json",
224
+ Authorization: `Bearer ${this.config.apiKey}`
225
+ },
226
+ body: JSON.stringify({
227
+ input,
228
+ tenantId: this.config.tenantId,
229
+ requestId,
230
+ userId: this.config.userId,
231
+ department: this.config.department,
232
+ aiModel: this.config.aiModel,
233
+ agentId: this.config.agentId ?? "sdk-client",
234
+ employeeName: this.config.employeeName ?? this.config.userId,
235
+ decision: result.decision,
236
+ reason: result.reason,
237
+ violatedRule: result.violatedRule,
238
+ requiresApproval: result.requiresApproval,
239
+ complianceMappings: result.complianceMappings
240
+ })
241
+ });
242
+ if (!res.ok) {
243
+ scopedLog.error("Failed to log interaction", { status: res.status });
244
+ }
245
+ return res.json();
246
+ }
247
+ };
248
+ var ShieldBlockedError = class extends Error {
249
+ constructor(reason, violatedRule, complianceMappings, sessionRevoked = false) {
250
+ super(`[G8R Shield BLOCKED] ${reason}`);
251
+ this.name = "ShieldBlockedError";
252
+ this.violatedRule = violatedRule;
253
+ this.complianceMappings = complianceMappings;
254
+ this.sessionRevoked = sessionRevoked;
255
+ }
256
+ };
257
+ // Annotate the CommonJS export names for ESM import in node:
258
+ 0 && (module.exports = {
259
+ AgentShield,
260
+ ShieldBlockedError
261
+ });
262
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,236 @@
1
+ // src/index.ts
2
+ import { match } from "ts-pattern";
3
+
4
+ // src/ids.ts
5
+ function newRequestId() {
6
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
7
+ return crypto.randomUUID();
8
+ }
9
+ return `req-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
10
+ }
11
+
12
+ // src/logger.ts
13
+ var LEVEL_ORDER = {
14
+ debug: 10,
15
+ info: 20,
16
+ warn: 30,
17
+ error: 40
18
+ };
19
+ function createLogger(opts = {}) {
20
+ const minLevel = LEVEL_ORDER[opts.level ?? "info"];
21
+ const sink = opts.sink ?? ((line) => console.log(line));
22
+ const bindings = opts.bindings ?? {};
23
+ function emit(level, msg, ctx) {
24
+ if (LEVEL_ORDER[level] < minLevel) return;
25
+ const payload = {
26
+ level,
27
+ msg,
28
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
29
+ ...bindings,
30
+ ...ctx
31
+ };
32
+ sink(JSON.stringify(payload));
33
+ }
34
+ return {
35
+ debug: (msg, ctx) => emit("debug", msg, ctx),
36
+ info: (msg, ctx) => emit("info", msg, ctx),
37
+ warn: (msg, ctx) => emit("warn", msg, ctx),
38
+ error: (msg, ctx) => emit("error", msg, ctx),
39
+ child: (extra) => createLogger({ ...opts, bindings: { ...bindings, ...extra } })
40
+ };
41
+ }
42
+ var log = createLogger();
43
+
44
+ // src/redaction.ts
45
+ function shannonEntropy(str) {
46
+ const freq = /* @__PURE__ */ new Map();
47
+ for (const ch of str) {
48
+ freq.set(ch, (freq.get(ch) ?? 0) + 1);
49
+ }
50
+ let entropy = 0;
51
+ const len = str.length;
52
+ for (const count of freq.values()) {
53
+ const p = count / len;
54
+ entropy -= p * Math.log2(p);
55
+ }
56
+ return entropy;
57
+ }
58
+ var BIP32_PATTERN = /\b[xyz](?:pub|prv)[a-zA-Z0-9]{99,111}\b/g;
59
+ var WIF_PATTERN = /\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\b/g;
60
+ var HEX_KEY_PATTERN = /\b(?:0x)?[0-9a-fA-F]{64}\b/g;
61
+ var PEM_PATTERN = /-----BEGIN (?:RSA |EC |OPENSSH )?(?:PRIVATE|PUBLIC) KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?(?:PRIVATE|PUBLIC) KEY-----/g;
62
+ var CUSTODIAL_ID_PATTERN = /\bcustodial-id:[A-Za-z0-9_-]+\b/g;
63
+ var CUST_PATTERN = /\bcust-\d+\b/gi;
64
+ var WALLET_ID_PATTERN = /\bwallet-id:[A-Za-z0-9_-]+\b/g;
65
+ var VAULT_ID_PATTERN = /\bvault-id:[A-Za-z0-9_-]+\b/g;
66
+ var ENTROPY_THRESHOLD = 4.5;
67
+ var ENTROPY_MIN_LENGTH = 32;
68
+ function extractHighEntropyTokens(input) {
69
+ const candidates = input.split(/[\s,;:"'`(){}[\]<>]+/).filter(Boolean);
70
+ return candidates.filter(
71
+ (token) => token.length >= ENTROPY_MIN_LENGTH && shannonEntropy(token) >= ENTROPY_THRESHOLD
72
+ );
73
+ }
74
+ function redactSensitiveData(input) {
75
+ const tokensReplaced = [];
76
+ let redacted = input;
77
+ function replaceAll(pattern, label) {
78
+ redacted = redacted.replace(pattern, (match2) => {
79
+ tokensReplaced.push(match2);
80
+ return `[REDACTED:${label}]`;
81
+ });
82
+ }
83
+ replaceAll(PEM_PATTERN, "PEM_KEY");
84
+ replaceAll(BIP32_PATTERN, "BIP32_KEY");
85
+ replaceAll(WIF_PATTERN, "WIF_KEY");
86
+ replaceAll(HEX_KEY_PATTERN, "HEX_KEY");
87
+ replaceAll(CUSTODIAL_ID_PATTERN, "CUSTODIAL_ID");
88
+ replaceAll(CUST_PATTERN, "CUST_ID");
89
+ replaceAll(WALLET_ID_PATTERN, "WALLET_ID");
90
+ replaceAll(VAULT_ID_PATTERN, "VAULT_ID");
91
+ const highEntropyTokens = extractHighEntropyTokens(redacted);
92
+ for (const token of highEntropyTokens) {
93
+ if (!redacted.includes(token)) continue;
94
+ tokensReplaced.push(token);
95
+ redacted = redacted.split(token).join("[REDACTED:HIGH_ENTROPY]");
96
+ }
97
+ return { redacted, tokensReplaced };
98
+ }
99
+
100
+ // src/index.ts
101
+ var AgentShield = class {
102
+ constructor(config) {
103
+ this.config = config;
104
+ }
105
+ /**
106
+ * Check a prompt against the policy engine before sending to the LLM.
107
+ *
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.
111
+ *
112
+ * Returns the policy decision without executing the LLM call.
113
+ */
114
+ async check(prompt, requestId = newRequestId()) {
115
+ const { redacted, tokensReplaced } = redactSensitiveData(prompt);
116
+ const scopedLog = log.child({
117
+ tenant_id: this.config.tenantId,
118
+ request_id: requestId
119
+ });
120
+ const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/check`, {
121
+ method: "POST",
122
+ headers: {
123
+ "Content-Type": "application/json",
124
+ Authorization: `Bearer ${this.config.apiKey}`
125
+ },
126
+ body: JSON.stringify({
127
+ input: redacted,
128
+ // send the redacted version — never the raw prompt
129
+ tenantId: this.config.tenantId,
130
+ requestId,
131
+ userId: this.config.userId,
132
+ department: this.config.department,
133
+ aiModel: this.config.aiModel,
134
+ agentId: this.config.agentId ?? "sdk-client"
135
+ })
136
+ });
137
+ if (!res.ok) {
138
+ scopedLog.error("Shield policy check failed", { status: res.status });
139
+ throw new Error(`Shield policy check failed: ${res.status}`);
140
+ }
141
+ const result = await res.json();
142
+ return {
143
+ ...result,
144
+ ...tokensReplaced.length > 0 ? { redactedTokens: tokensReplaced } : {}
145
+ };
146
+ }
147
+ /**
148
+ * Wrap an LLM call with policy enforcement.
149
+ *
150
+ * The `llmCallFactory` is a function that creates the LLM promise.
151
+ * It is only invoked if the policy engine allows the action.
152
+ * This prevents the LLM call from executing before the policy check completes.
153
+ *
154
+ * @param llmCallFactory - A function that returns the LLM call promise
155
+ * @param prompt - The prompt text to evaluate against the policy engine
156
+ * @returns The result of the LLM call if allowed
157
+ * @throws ShieldBlockedError if the policy engine blocks the action
158
+ *
159
+ * @example
160
+ * const result = await shield.wrap(
161
+ * () => openai.chat.completions.create({
162
+ * model: 'gpt-4o',
163
+ * messages: [{ role: 'user', content: prompt }],
164
+ * }),
165
+ * prompt
166
+ * );
167
+ */
168
+ async wrap(llmCallFactory, prompt) {
169
+ const requestId = newRequestId();
170
+ const policyResult = await this.check(prompt, requestId);
171
+ await this.log(prompt, policyResult, requestId);
172
+ match(policyResult.decision).with("blocked", () => {
173
+ throw new ShieldBlockedError(
174
+ policyResult.reason,
175
+ policyResult.violatedRule,
176
+ policyResult.complianceMappings,
177
+ policyResult.sessionRevoked ?? false
178
+ );
179
+ }).with("escalated", () => {
180
+ log.warn("Action escalated for human review", {
181
+ reason: policyResult.reason
182
+ });
183
+ }).with("allowed", () => {
184
+ }).exhaustive();
185
+ return llmCallFactory();
186
+ }
187
+ /**
188
+ * Log an interaction to the Agent Shield Console.
189
+ */
190
+ async log(input, result, requestId = newRequestId()) {
191
+ const scopedLog = log.child({
192
+ tenant_id: this.config.tenantId,
193
+ request_id: requestId
194
+ });
195
+ const res = await fetch(`${this.config.consoleUrl}/api/sdk/v1/log`, {
196
+ method: "POST",
197
+ headers: {
198
+ "Content-Type": "application/json",
199
+ Authorization: `Bearer ${this.config.apiKey}`
200
+ },
201
+ body: JSON.stringify({
202
+ input,
203
+ tenantId: this.config.tenantId,
204
+ requestId,
205
+ userId: this.config.userId,
206
+ department: this.config.department,
207
+ aiModel: this.config.aiModel,
208
+ agentId: this.config.agentId ?? "sdk-client",
209
+ employeeName: this.config.employeeName ?? this.config.userId,
210
+ decision: result.decision,
211
+ reason: result.reason,
212
+ violatedRule: result.violatedRule,
213
+ requiresApproval: result.requiresApproval,
214
+ complianceMappings: result.complianceMappings
215
+ })
216
+ });
217
+ if (!res.ok) {
218
+ scopedLog.error("Failed to log interaction", { status: res.status });
219
+ }
220
+ return res.json();
221
+ }
222
+ };
223
+ var ShieldBlockedError = class extends Error {
224
+ constructor(reason, violatedRule, complianceMappings, sessionRevoked = false) {
225
+ super(`[G8R Shield BLOCKED] ${reason}`);
226
+ this.name = "ShieldBlockedError";
227
+ this.violatedRule = violatedRule;
228
+ this.complianceMappings = complianceMappings;
229
+ this.sessionRevoked = sessionRevoked;
230
+ }
231
+ };
232
+ export {
233
+ AgentShield,
234
+ ShieldBlockedError
235
+ };
236
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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"]}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@g8r-security/agent-shield-sdk",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript client for G8R Agent Shield — wrap LLM and agent calls with policy enforcement, local-first redaction, and audit logging.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Gator-Security/g8r-agent-shield-sdk.git",
9
+ "directory": "js"
10
+ },
11
+ "homepage": "https://github.com/Gator-Security/g8r-agent-shield-sdk#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/Gator-Security/g8r-agent-shield-sdk/issues"
14
+ },
15
+ "keywords": [
16
+ "ai-governance",
17
+ "llm",
18
+ "llm-guardrails",
19
+ "policy-enforcement",
20
+ "agent-security",
21
+ "owasp-llm",
22
+ "compliance",
23
+ "audit-log"
24
+ ],
25
+ "main": "./dist/index.js",
26
+ "module": "./dist/index.mjs",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.mjs",
32
+ "require": "./dist/index.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "sideEffects": false,
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "dependencies": {
46
+ "ts-pattern": "^5.9.0"
47
+ },
48
+ "scripts": {
49
+ "build": "tsup",
50
+ "test": "jest",
51
+ "test:coverage": "jest --coverage",
52
+ "typecheck": "tsc --noEmit",
53
+ "prepublishOnly": "npm run typecheck && npm run build"
54
+ },
55
+ "devDependencies": {
56
+ "@jest/globals": "^30.3.0",
57
+ "@types/jest": "^30.0.0",
58
+ "@types/node": "^20",
59
+ "jest": "^30.3.0",
60
+ "ts-jest": "^29.0.0",
61
+ "tsup": "^8.3.0",
62
+ "typescript": "^5"
63
+ }
64
+ }