@g8r-security/agent-shield-sdk 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/index.d.mts +41 -2
- package/dist/index.d.ts +41 -2
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +14 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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',
|
package/dist/index.d.mts
CHANGED
|
@@ -17,6 +17,44 @@ type TenantId = string & {
|
|
|
17
17
|
type RequestId = string & {
|
|
18
18
|
readonly __brand: 'RequestId';
|
|
19
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* Cast a string to TenantId. Use at boundaries (config, env, request body).
|
|
22
|
+
* Enforces the charset / length contract: 1-64 chars of `[a-z0-9-]`,
|
|
23
|
+
* starting with `[a-z0-9]` — catches programmatic misuse (config typos,
|
|
24
|
+
* hard-coded slugs that drift).
|
|
25
|
+
*/
|
|
26
|
+
declare function tenantId(s: string): TenantId;
|
|
27
|
+
/** Generate a fresh request ID. Prefers crypto.randomUUID where available. */
|
|
28
|
+
declare function newRequestId(): RequestId;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* BitGo VPC Sensitive Data Redaction
|
|
32
|
+
*
|
|
33
|
+
* Redacts cryptographic keys, custodial identifiers, and high-entropy strings
|
|
34
|
+
* BEFORE sending prompts to the G8R policy gateway (local-first redaction layer).
|
|
35
|
+
*
|
|
36
|
+
* Compliance:
|
|
37
|
+
* - GDPR Art. 32: Security of Processing — appropriate technical measures
|
|
38
|
+
* - PCI-DSS 3.4: Render PAN/sensitive data unreadable wherever stored or transmitted
|
|
39
|
+
*/
|
|
40
|
+
interface RedactionResult {
|
|
41
|
+
/** The input with all sensitive tokens replaced by placeholder strings. */
|
|
42
|
+
redacted: string;
|
|
43
|
+
/** The original sensitive token strings that were replaced. */
|
|
44
|
+
tokensReplaced: string[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Redact sensitive data from a prompt string before it reaches the gateway.
|
|
48
|
+
*
|
|
49
|
+
* Processing order (important — PEM first to avoid splitting on inner patterns):
|
|
50
|
+
* 1. PEM private/public key blocks
|
|
51
|
+
* 2. BIP-32 extended keys
|
|
52
|
+
* 3. WIF private keys
|
|
53
|
+
* 4. Raw hex 256-bit keys
|
|
54
|
+
* 5. Custodial IDs (all four variants)
|
|
55
|
+
* 6. High-entropy string catch-all
|
|
56
|
+
*/
|
|
57
|
+
declare function redactSensitiveData(input: string): RedactionResult;
|
|
20
58
|
|
|
21
59
|
/**
|
|
22
60
|
* G8R Agent Shield SDK
|
|
@@ -27,11 +65,12 @@ type RequestId = string & {
|
|
|
27
65
|
* Agent Shield Console.
|
|
28
66
|
*
|
|
29
67
|
* Usage:
|
|
30
|
-
* import { AgentShield } from '@g8r-security/agent-shield-sdk';
|
|
68
|
+
* import { AgentShield, tenantId } from '@g8r-security/agent-shield-sdk';
|
|
31
69
|
*
|
|
32
70
|
* const shield = new AgentShield({
|
|
33
71
|
* consoleUrl: 'https://shield.yourcompany.com',
|
|
34
72
|
* apiKey: 'sk-shield-...',
|
|
73
|
+
* tenantId: tenantId('acme-corp'),
|
|
35
74
|
* department: 'Finance',
|
|
36
75
|
* userId: 'usr_FIN_042',
|
|
37
76
|
* aiModel: 'GPT-4o',
|
|
@@ -143,4 +182,4 @@ declare class ShieldBlockedError extends Error {
|
|
|
143
182
|
constructor(reason: string, violatedRule: string | null, complianceMappings: PolicyCheckResult['complianceMappings'], sessionRevoked?: boolean);
|
|
144
183
|
}
|
|
145
184
|
|
|
146
|
-
export { AgentShield, type PolicyCheckResult, ShieldBlockedError, type ShieldConfig, type ShieldLogEntry };
|
|
185
|
+
export { AgentShield, type PolicyCheckResult, type RedactionResult, type RequestId, ShieldBlockedError, type ShieldConfig, type ShieldLogEntry, type TenantId, newRequestId, redactSensitiveData, tenantId };
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,44 @@ type TenantId = string & {
|
|
|
17
17
|
type RequestId = string & {
|
|
18
18
|
readonly __brand: 'RequestId';
|
|
19
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* Cast a string to TenantId. Use at boundaries (config, env, request body).
|
|
22
|
+
* Enforces the charset / length contract: 1-64 chars of `[a-z0-9-]`,
|
|
23
|
+
* starting with `[a-z0-9]` — catches programmatic misuse (config typos,
|
|
24
|
+
* hard-coded slugs that drift).
|
|
25
|
+
*/
|
|
26
|
+
declare function tenantId(s: string): TenantId;
|
|
27
|
+
/** Generate a fresh request ID. Prefers crypto.randomUUID where available. */
|
|
28
|
+
declare function newRequestId(): RequestId;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* BitGo VPC Sensitive Data Redaction
|
|
32
|
+
*
|
|
33
|
+
* Redacts cryptographic keys, custodial identifiers, and high-entropy strings
|
|
34
|
+
* BEFORE sending prompts to the G8R policy gateway (local-first redaction layer).
|
|
35
|
+
*
|
|
36
|
+
* Compliance:
|
|
37
|
+
* - GDPR Art. 32: Security of Processing — appropriate technical measures
|
|
38
|
+
* - PCI-DSS 3.4: Render PAN/sensitive data unreadable wherever stored or transmitted
|
|
39
|
+
*/
|
|
40
|
+
interface RedactionResult {
|
|
41
|
+
/** The input with all sensitive tokens replaced by placeholder strings. */
|
|
42
|
+
redacted: string;
|
|
43
|
+
/** The original sensitive token strings that were replaced. */
|
|
44
|
+
tokensReplaced: string[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Redact sensitive data from a prompt string before it reaches the gateway.
|
|
48
|
+
*
|
|
49
|
+
* Processing order (important — PEM first to avoid splitting on inner patterns):
|
|
50
|
+
* 1. PEM private/public key blocks
|
|
51
|
+
* 2. BIP-32 extended keys
|
|
52
|
+
* 3. WIF private keys
|
|
53
|
+
* 4. Raw hex 256-bit keys
|
|
54
|
+
* 5. Custodial IDs (all four variants)
|
|
55
|
+
* 6. High-entropy string catch-all
|
|
56
|
+
*/
|
|
57
|
+
declare function redactSensitiveData(input: string): RedactionResult;
|
|
20
58
|
|
|
21
59
|
/**
|
|
22
60
|
* G8R Agent Shield SDK
|
|
@@ -27,11 +65,12 @@ type RequestId = string & {
|
|
|
27
65
|
* Agent Shield Console.
|
|
28
66
|
*
|
|
29
67
|
* Usage:
|
|
30
|
-
* import { AgentShield } from '@g8r-security/agent-shield-sdk';
|
|
68
|
+
* import { AgentShield, tenantId } from '@g8r-security/agent-shield-sdk';
|
|
31
69
|
*
|
|
32
70
|
* const shield = new AgentShield({
|
|
33
71
|
* consoleUrl: 'https://shield.yourcompany.com',
|
|
34
72
|
* apiKey: 'sk-shield-...',
|
|
73
|
+
* tenantId: tenantId('acme-corp'),
|
|
35
74
|
* department: 'Finance',
|
|
36
75
|
* userId: 'usr_FIN_042',
|
|
37
76
|
* aiModel: 'GPT-4o',
|
|
@@ -143,4 +182,4 @@ declare class ShieldBlockedError extends Error {
|
|
|
143
182
|
constructor(reason: string, violatedRule: string | null, complianceMappings: PolicyCheckResult['complianceMappings'], sessionRevoked?: boolean);
|
|
144
183
|
}
|
|
145
184
|
|
|
146
|
-
export { AgentShield, type PolicyCheckResult, ShieldBlockedError, type ShieldConfig, type ShieldLogEntry };
|
|
185
|
+
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();
|
|
@@ -257,6 +270,9 @@ var ShieldBlockedError = class extends Error {
|
|
|
257
270
|
// Annotate the CommonJS export names for ESM import in node:
|
|
258
271
|
0 && (module.exports = {
|
|
259
272
|
AgentShield,
|
|
260
|
-
ShieldBlockedError
|
|
273
|
+
ShieldBlockedError,
|
|
274
|
+
newRequestId,
|
|
275
|
+
redactSensitiveData,
|
|
276
|
+
tenantId
|
|
261
277
|
});
|
|
262
278
|
//# 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 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, 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 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
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();
|
|
@@ -231,6 +241,9 @@ var ShieldBlockedError = class extends Error {
|
|
|
231
241
|
};
|
|
232
242
|
export {
|
|
233
243
|
AgentShield,
|
|
234
|
-
ShieldBlockedError
|
|
244
|
+
ShieldBlockedError,
|
|
245
|
+
newRequestId,
|
|
246
|
+
redactSensitiveData,
|
|
247
|
+
tenantId
|
|
235
248
|
};
|
|
236
249
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.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 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, 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 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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@g8r-security/agent-shield-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
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": {
|