@blekline/contracts 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/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+
4
+ This package is licensed under Apache-2.0.
5
+ See https://github.com/Blekline/blekline-oss/blob/main/LICENSE-APACHE
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # @blekline/contracts
2
+
3
+ Shared types, Zod schemas, secret patterns, and local MCP tool enforcement for the Blekline ingress platform.
4
+
5
+ ## Exports
6
+
7
+ - `maskRequestSchema`, `MaskResponse`
8
+ - `eventIngestSchema`, `EventIngest`
9
+ - `enforceToolCallRequestSchema`, `enforceToolCallLocally`
10
+ - `McpToolPolicy`, `resolveMcpToolPolicyDecision`
11
+ - `scanTextForSecrets`, `BLEKLINE_HEADERS`
12
+
13
+ ## OpenAPI
14
+
15
+ Machine-readable API spec: [`openapi.yaml`](./openapi.yaml)
16
+
17
+ ## Build
18
+
19
+ ```bash
20
+ pnpm --filter @blekline/contracts build
21
+ ```
package/dist/auth.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ export declare const WORKSPACE_TOOL_SCOPES: readonly ["mask:write", "events:write", "events:read", "integrations:write"];
2
+ export type WorkspaceToolScope = (typeof WORKSPACE_TOOL_SCOPES)[number];
3
+ export declare const BLEKLINE_HEADERS: {
4
+ readonly workspaceToken: "x-blekline-workspace-token";
5
+ readonly workspaceId: "x-blekline-workspace-id";
6
+ readonly requestId: "x-request-id";
7
+ readonly clientSurface: "x-blekline-client-surface";
8
+ readonly modelProvider: "x-blekline-model-provider";
9
+ readonly modelId: "x-blekline-model-id";
10
+ };
11
+ export type ClientSurface = "cursor" | "claude-desktop" | "codex" | "sdk" | "extension" | "unknown";
12
+ export type ModelProvider = "anthropic" | "openai" | "google" | "xai" | "cursor" | "unknown";
13
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,8EAKxB,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,eAAO,MAAM,gBAAgB;;;;;;;CAOnB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,gBAAgB,GAAG,OAAO,GAAG,KAAK,GAAG,WAAW,GAAG,SAAS,CAAC;AAEpG,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC"}
package/dist/auth.js ADDED
@@ -0,0 +1,14 @@
1
+ export const WORKSPACE_TOOL_SCOPES = [
2
+ "mask:write",
3
+ "events:write",
4
+ "events:read",
5
+ "integrations:write",
6
+ ];
7
+ export const BLEKLINE_HEADERS = {
8
+ workspaceToken: "x-blekline-workspace-token",
9
+ workspaceId: "x-blekline-workspace-id",
10
+ requestId: "x-request-id",
11
+ clientSurface: "x-blekline-client-surface",
12
+ modelProvider: "x-blekline-model-provider",
13
+ modelId: "x-blekline-model-id",
14
+ };
@@ -0,0 +1,9 @@
1
+ import type { EnforceToolCallResult } from "./enforcement.js";
2
+ import type { McpToolPolicy } from "./mcp-policy.js";
3
+ export declare function enforceToolCallLocally(input: {
4
+ toolName: string;
5
+ arguments: Record<string, unknown>;
6
+ requestId: string;
7
+ mcpToolPolicy?: McpToolPolicy;
8
+ }): EnforceToolCallResult;
9
+ //# sourceMappingURL=enforce-local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enforce-local.d.ts","sourceRoot":"","sources":["../src/enforce-local.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAsC,MAAM,kBAAkB,CAAC;AAClG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA+DrD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B,GAAG,qBAAqB,CAyDxB"}
@@ -0,0 +1,108 @@
1
+ import { resolveMcpToolPolicyDecision } from "./mcp-policy.js";
2
+ import { scanTextForSecrets } from "./secret-patterns.js";
3
+ const DESTRUCTIVE_RE = /\brm\s+-rf\b|\bformat\s+c:\b|\bdrop\s+database\b/i;
4
+ function stringifyArgs(args) {
5
+ try {
6
+ return JSON.stringify(args);
7
+ }
8
+ catch {
9
+ return String(args);
10
+ }
11
+ }
12
+ function maskStringValue(input) {
13
+ const findings = scanTextForSecrets(input);
14
+ if (findings.length === 0)
15
+ return { text: input, count: 0 };
16
+ let out = input;
17
+ let offset = 0;
18
+ let count = 0;
19
+ const sorted = [...findings].sort((a, b) => a.start - b.start);
20
+ for (const f of sorted) {
21
+ const start = f.start + offset;
22
+ const end = f.end + offset;
23
+ const token = `[${f.label}]`;
24
+ out = out.slice(0, start) + token + out.slice(end);
25
+ offset += token.length - (f.end - f.start);
26
+ count += 1;
27
+ }
28
+ return { text: out, count };
29
+ }
30
+ function maskDeep(value, fieldPath, findings) {
31
+ if (typeof value === "string") {
32
+ const scan = scanTextForSecrets(value);
33
+ for (const s of scan) {
34
+ findings.push({ id: s.id, label: s.label, field: fieldPath });
35
+ }
36
+ const masked = maskStringValue(value);
37
+ return { value: masked.text, count: masked.count };
38
+ }
39
+ if (Array.isArray(value)) {
40
+ let count = 0;
41
+ const next = value.map((item, i) => {
42
+ const r = maskDeep(item, `${fieldPath}[${i}]`, findings);
43
+ count += r.count;
44
+ return r.value;
45
+ });
46
+ return { value: next, count };
47
+ }
48
+ if (value && typeof value === "object") {
49
+ let count = 0;
50
+ const next = {};
51
+ for (const [k, v] of Object.entries(value)) {
52
+ const r = maskDeep(v, fieldPath ? `${fieldPath}.${k}` : k, findings);
53
+ count += r.count;
54
+ next[k] = r.value;
55
+ }
56
+ return { value: next, count };
57
+ }
58
+ return { value, count: 0 };
59
+ }
60
+ export function enforceToolCallLocally(input) {
61
+ const findings = [];
62
+ const blob = stringifyArgs(input.arguments);
63
+ if (DESTRUCTIVE_RE.test(blob)) {
64
+ findings.push({ id: "destructive_command", label: "DESTRUCTIVE", field: "arguments" });
65
+ return {
66
+ action: "block",
67
+ maskedArguments: input.arguments,
68
+ findings,
69
+ entitiesMasked: 0,
70
+ riskTier: "high",
71
+ requestId: input.requestId,
72
+ };
73
+ }
74
+ const secretScan = scanTextForSecrets(blob);
75
+ for (const s of secretScan) {
76
+ findings.push({ id: s.id, label: s.label });
77
+ }
78
+ const masked = maskDeep(input.arguments, "", findings);
79
+ const entitiesMasked = masked.count;
80
+ let action = "allow";
81
+ let riskTier = "low";
82
+ const hasHighRiskSecret = secretScan.some((s) => ["aws_access_key", "openai_sk", "openai_sk_proj", "stripe_sk", "jwt", "ssn"].includes(s.id));
83
+ if (hasHighRiskSecret && entitiesMasked > 0) {
84
+ action = "mask";
85
+ riskTier = "high";
86
+ }
87
+ else if (entitiesMasked > 0) {
88
+ action = "mask";
89
+ riskTier = "medium";
90
+ }
91
+ if (input.mcpToolPolicy) {
92
+ action = resolveMcpToolPolicyDecision(input.mcpToolPolicy, input.toolName, action);
93
+ if (action === "block") {
94
+ riskTier = "high";
95
+ if (!findings.some((f) => f.id === "policy_denied")) {
96
+ findings.push({ id: "policy_denied", label: "POLICY", field: "toolName" });
97
+ }
98
+ }
99
+ }
100
+ return {
101
+ action,
102
+ maskedArguments: masked.value,
103
+ findings,
104
+ entitiesMasked,
105
+ riskTier,
106
+ requestId: input.requestId,
107
+ };
108
+ }
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ export type EnforcementAction = "allow" | "mask" | "block";
3
+ export declare const enforceToolCallRequestSchema: z.ZodObject<{
4
+ toolName: z.ZodString;
5
+ arguments: z.ZodRecord<z.ZodString, z.ZodUnknown>;
6
+ platform: z.ZodOptional<z.ZodString>;
7
+ clientSurface: z.ZodOptional<z.ZodEnum<{
8
+ cursor: "cursor";
9
+ "claude-desktop": "claude-desktop";
10
+ codex: "codex";
11
+ sdk: "sdk";
12
+ extension: "extension";
13
+ unknown: "unknown";
14
+ }>>;
15
+ }, z.core.$strip>;
16
+ export type EnforceToolCallRequest = z.infer<typeof enforceToolCallRequestSchema>;
17
+ export type ToolCallFinding = {
18
+ id: string;
19
+ label: string;
20
+ field?: string;
21
+ };
22
+ export type EnforceToolCallResult = {
23
+ action: EnforcementAction;
24
+ maskedArguments: Record<string, unknown>;
25
+ findings: ToolCallFinding[];
26
+ entitiesMasked: number;
27
+ riskTier: "low" | "medium" | "high";
28
+ requestId: string;
29
+ };
30
+ //# sourceMappingURL=enforcement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enforcement.d.ts","sourceRoot":"","sources":["../src/enforcement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,eAAO,MAAM,4BAA4B;;;;;;;;;;;;iBAKvC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ export const enforceToolCallRequestSchema = z.object({
3
+ toolName: z.string().min(1).max(120),
4
+ arguments: z.record(z.string(), z.unknown()),
5
+ platform: z.string().max(40).optional(),
6
+ clientSurface: z.enum(["cursor", "claude-desktop", "codex", "sdk", "extension", "unknown"]).optional(),
7
+ });
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ export declare const eventIngestSchema: z.ZodObject<{
3
+ platform: z.ZodOptional<z.ZodString>;
4
+ kind: z.ZodOptional<z.ZodString>;
5
+ entitiesMasked: z.ZodOptional<z.ZodNumber>;
6
+ riskTier: z.ZodOptional<z.ZodEnum<{
7
+ low: "low";
8
+ medium: "medium";
9
+ high: "high";
10
+ }>>;
11
+ sourceHost: z.ZodOptional<z.ZodString>;
12
+ action: z.ZodOptional<z.ZodString>;
13
+ maskProvider: z.ZodOptional<z.ZodEnum<{
14
+ azure: "azure";
15
+ fallback_local: "fallback_local";
16
+ }>>;
17
+ maskPhase: z.ZodOptional<z.ZodString>;
18
+ clientSurface: z.ZodOptional<z.ZodEnum<{
19
+ cursor: "cursor";
20
+ "claude-desktop": "claude-desktop";
21
+ codex: "codex";
22
+ sdk: "sdk";
23
+ extension: "extension";
24
+ unknown: "unknown";
25
+ }>>;
26
+ modelProvider: z.ZodOptional<z.ZodEnum<{
27
+ cursor: "cursor";
28
+ unknown: "unknown";
29
+ anthropic: "anthropic";
30
+ openai: "openai";
31
+ google: "google";
32
+ xai: "xai";
33
+ }>>;
34
+ modelId: z.ZodOptional<z.ZodString>;
35
+ mcpToolName: z.ZodOptional<z.ZodString>;
36
+ downstreamServer: z.ZodOptional<z.ZodString>;
37
+ }, z.core.$strip>;
38
+ export type EventIngest = z.infer<typeof eventIngestSchema>;
39
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAc5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
package/dist/events.js ADDED
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ export const eventIngestSchema = z.object({
3
+ platform: z.string().max(40).optional(),
4
+ kind: z.string().max(48).optional(),
5
+ entitiesMasked: z.number().int().min(0).max(5000).optional(),
6
+ riskTier: z.enum(["low", "medium", "high"]).optional(),
7
+ sourceHost: z.string().max(253).optional(),
8
+ action: z.string().max(48).optional(),
9
+ maskProvider: z.enum(["azure", "fallback_local"]).optional(),
10
+ maskPhase: z.string().max(48).optional(),
11
+ clientSurface: z.enum(["cursor", "claude-desktop", "codex", "sdk", "extension", "unknown"]).optional(),
12
+ modelProvider: z.enum(["anthropic", "openai", "google", "xai", "cursor", "unknown"]).optional(),
13
+ modelId: z.string().max(80).optional(),
14
+ mcpToolName: z.string().max(120).optional(),
15
+ downstreamServer: z.string().max(80).optional(),
16
+ });
@@ -0,0 +1,9 @@
1
+ export * from "./mcp-policy.js";
2
+ export * from "./auth.js";
3
+ export * from "./mask.js";
4
+ export * from "./events.js";
5
+ export * from "./policy.js";
6
+ export * from "./enforcement.js";
7
+ export * from "./enforce-local.js";
8
+ export * from "./secret-patterns.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./mcp-policy.js";
2
+ export * from "./auth.js";
3
+ export * from "./mask.js";
4
+ export * from "./events.js";
5
+ export * from "./policy.js";
6
+ export * from "./enforcement.js";
7
+ export * from "./enforce-local.js";
8
+ export * from "./secret-patterns.js";
package/dist/mask.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ export declare const maskRequestSchema: z.ZodObject<{
3
+ text: z.ZodString;
4
+ platform: z.ZodOptional<z.ZodString>;
5
+ }, z.core.$strip>;
6
+ export type MaskRequest = z.infer<typeof maskRequestSchema>;
7
+ export type WireRedactionAction = "mask_and_send" | "mask_and_confirm" | "block_and_review";
8
+ export type MaskResponse = {
9
+ maskedText: string;
10
+ tokenMap: Record<string, string>;
11
+ entitiesMasked: number;
12
+ platform: string;
13
+ provider: "azure" | "fallback_local";
14
+ requestId: string;
15
+ piiLanguage?: string;
16
+ decision?: WireRedactionAction;
17
+ blocked?: boolean;
18
+ blockReason?: string;
19
+ };
20
+ export type MaskErrorCode = "billing_required" | "credits_exhausted" | "prompt_limit_exceeded" | "mask_fallback_blocked" | "high_risk_literal_remaining" | "plan_upgrade_required";
21
+ //# sourceMappingURL=mask.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask.d.ts","sourceRoot":"","sources":["../src/mask.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,iBAAiB;;;iBAG5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AAE5F,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,GAAG,gBAAgB,CAAC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,kBAAkB,GAClB,mBAAmB,GACnB,uBAAuB,GACvB,uBAAuB,GACvB,6BAA6B,GAC7B,uBAAuB,CAAC"}
package/dist/mask.js ADDED
@@ -0,0 +1,5 @@
1
+ import { z } from "zod";
2
+ export const maskRequestSchema = z.object({
3
+ text: z.string().min(1),
4
+ platform: z.string().max(40).optional(),
5
+ });
@@ -0,0 +1,11 @@
1
+ export type McpToolPolicyAction = "allow" | "mask" | "block";
2
+ export type McpToolPolicy = {
3
+ allowedTools: string[];
4
+ deniedTools: string[];
5
+ defaultAction: McpToolPolicyAction;
6
+ };
7
+ export declare const DEFAULT_MCP_TOOL_POLICY: McpToolPolicy;
8
+ export declare function normalizeMcpToolPolicy(raw: unknown): McpToolPolicy;
9
+ /** Resolve workspace MCP tool policy before local/cloud enforcement. */
10
+ export declare function resolveMcpToolPolicyDecision(policy: McpToolPolicy, toolName: string, localAction: McpToolPolicyAction): McpToolPolicyAction;
11
+ //# sourceMappingURL=mcp-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-policy.d.ts","sourceRoot":"","sources":["../src/mcp-policy.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAE7D,MAAM,MAAM,aAAa,GAAG;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,mBAAmB,CAAC;CACpC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,aAIrC,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,CAclE;AAED,wEAAwE;AACxE,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,mBAAmB,GAC/B,mBAAmB,CAWrB"}
@@ -0,0 +1,38 @@
1
+ export const DEFAULT_MCP_TOOL_POLICY = {
2
+ allowedTools: [],
3
+ deniedTools: [],
4
+ defaultAction: "mask",
5
+ };
6
+ export function normalizeMcpToolPolicy(raw) {
7
+ if (!raw || typeof raw !== "object")
8
+ return { ...DEFAULT_MCP_TOOL_POLICY };
9
+ const o = raw;
10
+ const allowedTools = Array.isArray(o.allowedTools)
11
+ ? o.allowedTools.filter((t) => typeof t === "string").map((t) => t.trim()).filter(Boolean).slice(0, 64)
12
+ : [];
13
+ const deniedTools = Array.isArray(o.deniedTools)
14
+ ? o.deniedTools.filter((t) => typeof t === "string").map((t) => t.trim()).filter(Boolean).slice(0, 64)
15
+ : [];
16
+ const defaultAction = o.defaultAction === "allow" || o.defaultAction === "mask" || o.defaultAction === "block"
17
+ ? o.defaultAction
18
+ : "mask";
19
+ return { allowedTools, deniedTools, defaultAction };
20
+ }
21
+ /** Resolve workspace MCP tool policy before local/cloud enforcement. */
22
+ export function resolveMcpToolPolicyDecision(policy, toolName, localAction) {
23
+ const name = toolName.trim().toLowerCase();
24
+ if (policy.deniedTools.some((t) => t.toLowerCase() === name))
25
+ return "block";
26
+ if (policy.allowedTools.length > 0) {
27
+ const allowed = policy.allowedTools.some((t) => t.toLowerCase() === name);
28
+ if (!allowed)
29
+ return "block";
30
+ }
31
+ if (localAction === "block")
32
+ return "block";
33
+ if (policy.defaultAction === "block")
34
+ return "block";
35
+ if (localAction === "mask")
36
+ return "mask";
37
+ return policy.defaultAction;
38
+ }
@@ -0,0 +1,15 @@
1
+ import type { WireRedactionAction } from "./mask.js";
2
+ export type PromptRiskTier = "low" | "medium" | "high";
3
+ export type PolicySimulation = {
4
+ platform: string;
5
+ risk: PromptRiskTier;
6
+ action: WireRedactionAction;
7
+ matchedKeywords: string[];
8
+ shieldEnabled: boolean;
9
+ };
10
+ export type PolicySimulateRequest = {
11
+ prompt: string;
12
+ platform?: string;
13
+ sourceHost?: string;
14
+ };
15
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAErD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEvD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC"}
package/dist/policy.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ /** Portable secret/PII patterns for local fast scan (aligned with webapp detectors). */
2
+ export type SecretPattern = {
3
+ id: string;
4
+ label: string;
5
+ pattern: RegExp;
6
+ };
7
+ export declare function buildSecretPatterns(): SecretPattern[];
8
+ export type ScanFinding = {
9
+ id: string;
10
+ label: string;
11
+ match: string;
12
+ start: number;
13
+ end: number;
14
+ };
15
+ export declare function scanTextForSecrets(text: string): ScanFinding[];
16
+ //# sourceMappingURL=secret-patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-patterns.d.ts","sourceRoot":"","sources":["../src/secret-patterns.ts"],"names":[],"mappings":"AAAA,wFAAwF;AAExF,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,wBAAgB,mBAAmB,IAAI,aAAa,EAAE,CAgBrD;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAY9D"}
@@ -0,0 +1,32 @@
1
+ /** Portable secret/PII patterns for local fast scan (aligned with webapp detectors). */
2
+ export function buildSecretPatterns() {
3
+ return [
4
+ { id: "aws_access_key", label: "AWS_KEY", pattern: /\b(?:AKIA|ASIA|AIDA|AROA|AGPA|AIPA|ANPA|ANVA|APKA|ASCA|ACCA)[A-Z0-9]{16}\b/g },
5
+ { id: "github_pat", label: "GITHUB", pattern: /\bgh[oprus]_[A-Za-z0-9_]{36,255}\b/g },
6
+ { id: "github_pat_fine", label: "GITHUB_FINE", pattern: /\bgithub_pat_[A-Za-z0-9_]{80,500}\b/g },
7
+ { id: "openai_sk", label: "OPENAI", pattern: /\bsk-[A-Za-z0-9]{20,}\b/g },
8
+ { id: "openai_sk_proj", label: "OPENAI_PROJ", pattern: /\bsk-proj-[A-Za-z0-9_-]{20,500}\b/g },
9
+ { id: "stripe_sk", label: "STRIPE", pattern: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,128}\b/g },
10
+ { id: "slack_token", label: "SLACK", pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,128}\b/g },
11
+ { id: "google_api_key", label: "GOOGLE_KEY", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
12
+ { id: "jwt", label: "JWT", pattern: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g },
13
+ { id: "email", label: "EMAIL", pattern: /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi },
14
+ { id: "ssn", label: "SSN", pattern: /\b\d{3}-\d{2}-\d{4}\b/g },
15
+ { id: "iban", label: "IBAN", pattern: /\b[A-Z]{2}\d{2}(?:[ -]?[A-Z0-9]){11,30}\b/gi },
16
+ { id: "card", label: "CARD", pattern: /\b(?:\d[ -]?){12,19}\b/g },
17
+ ];
18
+ }
19
+ export function scanTextForSecrets(text) {
20
+ const findings = [];
21
+ for (const { id, label, pattern } of buildSecretPatterns()) {
22
+ const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
23
+ let m;
24
+ while ((m = re.exec(text)) !== null) {
25
+ const match = m[0];
26
+ if (match.includes("[") && match.includes("]"))
27
+ continue;
28
+ findings.push({ id, label, match, start: m.index, end: m.index + match.length });
29
+ }
30
+ }
31
+ return findings;
32
+ }
package/openapi.yaml ADDED
@@ -0,0 +1,248 @@
1
+ openapi: 3.1.0
2
+ info:
3
+ title: Blekline Ingress Control Plane API
4
+ version: 1.0.0
5
+ description: |
6
+ Enterprise ingress governance for prompts, events, MCP tool calls, and universal LLM proxies.
7
+ servers:
8
+ - url: https://app.blekline.com
9
+ - url: http://localhost:3000
10
+ security:
11
+ - workspaceToken: []
12
+ components:
13
+ securitySchemes:
14
+ workspaceToken:
15
+ type: apiKey
16
+ in: header
17
+ name: x-blekline-workspace-token
18
+ schemas:
19
+ MaskRequest:
20
+ type: object
21
+ required: [text]
22
+ properties:
23
+ text:
24
+ type: string
25
+ platform:
26
+ type: string
27
+ MaskResponse:
28
+ type: object
29
+ properties:
30
+ maskedText:
31
+ type: string
32
+ entitiesMasked:
33
+ type: integer
34
+ decision:
35
+ type: string
36
+ enum: [mask_and_send, mask_and_confirm, block_and_review]
37
+ provider:
38
+ type: string
39
+ requestId:
40
+ type: string
41
+ latencyMs:
42
+ type: number
43
+ description: Total request latency in milliseconds
44
+ maskPath:
45
+ type: string
46
+ enum: [azure_first, local_first, local_only]
47
+ region:
48
+ type: string
49
+ description: Ingress region label (x-blekline-ingress-region)
50
+ EnforceToolCallRequest:
51
+ type: object
52
+ required: [toolName, arguments]
53
+ properties:
54
+ toolName:
55
+ type: string
56
+ arguments:
57
+ type: object
58
+ platform:
59
+ type: string
60
+ clientSurface:
61
+ type: string
62
+ enum: [cursor, claude-desktop, codex, sdk, extension, unknown]
63
+ EnforceToolCallResult:
64
+ type: object
65
+ properties:
66
+ action:
67
+ type: string
68
+ enum: [allow, mask, block]
69
+ maskedArguments:
70
+ type: object
71
+ entitiesMasked:
72
+ type: integer
73
+ riskTier:
74
+ type: string
75
+ enum: [low, medium, high]
76
+ requestId:
77
+ type: string
78
+ McpToolPolicy:
79
+ type: object
80
+ properties:
81
+ allowedTools:
82
+ type: array
83
+ items:
84
+ type: string
85
+ deniedTools:
86
+ type: array
87
+ items:
88
+ type: string
89
+ defaultAction:
90
+ type: string
91
+ enum: [allow, mask, block]
92
+ paths:
93
+ /api/mask:
94
+ post:
95
+ summary: Mask prompt text
96
+ operationId: maskPrompt
97
+ requestBody:
98
+ required: true
99
+ content:
100
+ application/json:
101
+ schema:
102
+ $ref: "#/components/schemas/MaskRequest"
103
+ responses:
104
+ "200":
105
+ description: Masked prompt
106
+ content:
107
+ application/json:
108
+ schema:
109
+ $ref: "#/components/schemas/MaskResponse"
110
+ /api/events:
111
+ post:
112
+ summary: Emit governance event (metadata-only)
113
+ operationId: emitEvent
114
+ requestBody:
115
+ required: true
116
+ content:
117
+ application/json:
118
+ schema:
119
+ type: object
120
+ required: [kind, platform]
121
+ properties:
122
+ kind:
123
+ type: string
124
+ platform:
125
+ type: string
126
+ entitiesMasked:
127
+ type: integer
128
+ riskTier:
129
+ type: string
130
+ action:
131
+ type: string
132
+ clientSurface:
133
+ type: string
134
+ modelProvider:
135
+ type: string
136
+ modelId:
137
+ type: string
138
+ responses:
139
+ "200":
140
+ description: Event accepted
141
+ /api/mcp/enforce-tool-call:
142
+ post:
143
+ summary: Enforce MCP tool call policy
144
+ operationId: enforceToolCall
145
+ requestBody:
146
+ required: true
147
+ content:
148
+ application/json:
149
+ schema:
150
+ $ref: "#/components/schemas/EnforceToolCallRequest"
151
+ responses:
152
+ "200":
153
+ description: Enforcement decision
154
+ content:
155
+ application/json:
156
+ schema:
157
+ $ref: "#/components/schemas/EnforceToolCallResult"
158
+ /api/policy/simulate:
159
+ post:
160
+ summary: Simulate redaction policy on a prompt
161
+ operationId: simulatePolicy
162
+ requestBody:
163
+ required: true
164
+ content:
165
+ application/json:
166
+ schema:
167
+ type: object
168
+ required: [prompt]
169
+ properties:
170
+ prompt:
171
+ type: string
172
+ platform:
173
+ type: string
174
+ sourceHost:
175
+ type: string
176
+ responses:
177
+ "200":
178
+ description: Simulation result
179
+ /api/workspace/mcp-policy:
180
+ get:
181
+ summary: Read workspace MCP tool policy
182
+ operationId: getMcpToolPolicy
183
+ responses:
184
+ "200":
185
+ description: Current policy
186
+ content:
187
+ application/json:
188
+ schema:
189
+ type: object
190
+ properties:
191
+ mcpToolPolicy:
192
+ $ref: "#/components/schemas/McpToolPolicy"
193
+ post:
194
+ summary: Update workspace MCP tool policy
195
+ operationId: updateMcpToolPolicy
196
+ requestBody:
197
+ required: true
198
+ content:
199
+ application/json:
200
+ schema:
201
+ $ref: "#/components/schemas/McpToolPolicy"
202
+ responses:
203
+ "200":
204
+ description: Updated policy
205
+ /api/ingress/v1/chat/completions:
206
+ post:
207
+ summary: OpenAI-compatible ingress proxy (masks user messages)
208
+ operationId: ingressOpenAiChatCompletions
209
+ requestBody:
210
+ required: true
211
+ content:
212
+ application/json:
213
+ schema:
214
+ type: object
215
+ properties:
216
+ model:
217
+ type: string
218
+ messages:
219
+ type: array
220
+ items:
221
+ type: object
222
+ responses:
223
+ "200":
224
+ description: Upstream completion response
225
+ "403":
226
+ description: Blocked by ingress policy
227
+ /api/ingress/v1/messages:
228
+ post:
229
+ summary: Anthropic Messages API ingress proxy
230
+ operationId: ingressAnthropicMessages
231
+ requestBody:
232
+ required: true
233
+ content:
234
+ application/json:
235
+ schema:
236
+ type: object
237
+ properties:
238
+ model:
239
+ type: string
240
+ messages:
241
+ type: array
242
+ items:
243
+ type: object
244
+ responses:
245
+ "200":
246
+ description: Upstream message response
247
+ "403":
248
+ description: Blocked by ingress policy
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@blekline/contracts",
3
+ "version": "0.1.0",
4
+ "license": "Apache-2.0",
5
+ "private": false,
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/Blekline/blekline-oss.git",
9
+ "directory": "packages/contracts"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "type": "module",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js"
21
+ }
22
+ },
23
+ "devDependencies": {
24
+ "typescript": "^5.7.3"
25
+ },
26
+ "dependencies": {
27
+ "zod": "^4.3.6"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "typecheck": "tsc --noEmit"
32
+ }
33
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,21 @@
1
+ export const WORKSPACE_TOOL_SCOPES = [
2
+ "mask:write",
3
+ "events:write",
4
+ "events:read",
5
+ "integrations:write",
6
+ ] as const;
7
+
8
+ export type WorkspaceToolScope = (typeof WORKSPACE_TOOL_SCOPES)[number];
9
+
10
+ export const BLEKLINE_HEADERS = {
11
+ workspaceToken: "x-blekline-workspace-token",
12
+ workspaceId: "x-blekline-workspace-id",
13
+ requestId: "x-request-id",
14
+ clientSurface: "x-blekline-client-surface",
15
+ modelProvider: "x-blekline-model-provider",
16
+ modelId: "x-blekline-model-id",
17
+ } as const;
18
+
19
+ export type ClientSurface = "cursor" | "claude-desktop" | "codex" | "sdk" | "extension" | "unknown";
20
+
21
+ export type ModelProvider = "anthropic" | "openai" | "google" | "xai" | "cursor" | "unknown";
@@ -0,0 +1,127 @@
1
+ import type { EnforceToolCallResult, EnforcementAction, ToolCallFinding } from "./enforcement.js";
2
+ import type { McpToolPolicy } from "./mcp-policy.js";
3
+ import { resolveMcpToolPolicyDecision } from "./mcp-policy.js";
4
+ import { scanTextForSecrets } from "./secret-patterns.js";
5
+
6
+ const DESTRUCTIVE_RE = /\brm\s+-rf\b|\bformat\s+c:\b|\bdrop\s+database\b/i;
7
+
8
+ function stringifyArgs(args: Record<string, unknown>): string {
9
+ try {
10
+ return JSON.stringify(args);
11
+ } catch {
12
+ return String(args);
13
+ }
14
+ }
15
+
16
+ function maskStringValue(input: string): { text: string; count: number } {
17
+ const findings = scanTextForSecrets(input);
18
+ if (findings.length === 0) return { text: input, count: 0 };
19
+ let out = input;
20
+ let offset = 0;
21
+ let count = 0;
22
+ const sorted = [...findings].sort((a, b) => a.start - b.start);
23
+ for (const f of sorted) {
24
+ const start = f.start + offset;
25
+ const end = f.end + offset;
26
+ const token = `[${f.label}]`;
27
+ out = out.slice(0, start) + token + out.slice(end);
28
+ offset += token.length - (f.end - f.start);
29
+ count += 1;
30
+ }
31
+ return { text: out, count };
32
+ }
33
+
34
+ function maskDeep(value: unknown, fieldPath: string, findings: ToolCallFinding[]): { value: unknown; count: number } {
35
+ if (typeof value === "string") {
36
+ const scan = scanTextForSecrets(value);
37
+ for (const s of scan) {
38
+ findings.push({ id: s.id, label: s.label, field: fieldPath });
39
+ }
40
+ const masked = maskStringValue(value);
41
+ return { value: masked.text, count: masked.count };
42
+ }
43
+ if (Array.isArray(value)) {
44
+ let count = 0;
45
+ const next = value.map((item, i) => {
46
+ const r = maskDeep(item, `${fieldPath}[${i}]`, findings);
47
+ count += r.count;
48
+ return r.value;
49
+ });
50
+ return { value: next, count };
51
+ }
52
+ if (value && typeof value === "object") {
53
+ let count = 0;
54
+ const next: Record<string, unknown> = {};
55
+ for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
56
+ const r = maskDeep(v, fieldPath ? `${fieldPath}.${k}` : k, findings);
57
+ count += r.count;
58
+ next[k] = r.value;
59
+ }
60
+ return { value: next, count };
61
+ }
62
+ return { value, count: 0 };
63
+ }
64
+
65
+ export function enforceToolCallLocally(input: {
66
+ toolName: string;
67
+ arguments: Record<string, unknown>;
68
+ requestId: string;
69
+ mcpToolPolicy?: McpToolPolicy;
70
+ }): EnforceToolCallResult {
71
+ const findings: ToolCallFinding[] = [];
72
+ const blob = stringifyArgs(input.arguments);
73
+
74
+ if (DESTRUCTIVE_RE.test(blob)) {
75
+ findings.push({ id: "destructive_command", label: "DESTRUCTIVE", field: "arguments" });
76
+ return {
77
+ action: "block",
78
+ maskedArguments: input.arguments,
79
+ findings,
80
+ entitiesMasked: 0,
81
+ riskTier: "high",
82
+ requestId: input.requestId,
83
+ };
84
+ }
85
+
86
+ const secretScan = scanTextForSecrets(blob);
87
+ for (const s of secretScan) {
88
+ findings.push({ id: s.id, label: s.label });
89
+ }
90
+
91
+ const masked = maskDeep(input.arguments, "", findings);
92
+ const entitiesMasked = masked.count;
93
+
94
+ let action: EnforcementAction = "allow";
95
+ let riskTier: "low" | "medium" | "high" = "low";
96
+
97
+ const hasHighRiskSecret = secretScan.some((s) =>
98
+ ["aws_access_key", "openai_sk", "openai_sk_proj", "stripe_sk", "jwt", "ssn"].includes(s.id)
99
+ );
100
+
101
+ if (hasHighRiskSecret && entitiesMasked > 0) {
102
+ action = "mask";
103
+ riskTier = "high";
104
+ } else if (entitiesMasked > 0) {
105
+ action = "mask";
106
+ riskTier = "medium";
107
+ }
108
+
109
+ if (input.mcpToolPolicy) {
110
+ action = resolveMcpToolPolicyDecision(input.mcpToolPolicy, input.toolName, action);
111
+ if (action === "block") {
112
+ riskTier = "high";
113
+ if (!findings.some((f) => f.id === "policy_denied")) {
114
+ findings.push({ id: "policy_denied", label: "POLICY", field: "toolName" });
115
+ }
116
+ }
117
+ }
118
+
119
+ return {
120
+ action,
121
+ maskedArguments: masked.value as Record<string, unknown>,
122
+ findings,
123
+ entitiesMasked,
124
+ riskTier,
125
+ requestId: input.requestId,
126
+ };
127
+ }
@@ -0,0 +1,27 @@
1
+ import { z } from "zod";
2
+
3
+ export type EnforcementAction = "allow" | "mask" | "block";
4
+
5
+ export const enforceToolCallRequestSchema = z.object({
6
+ toolName: z.string().min(1).max(120),
7
+ arguments: z.record(z.string(), z.unknown()),
8
+ platform: z.string().max(40).optional(),
9
+ clientSurface: z.enum(["cursor", "claude-desktop", "codex", "sdk", "extension", "unknown"]).optional(),
10
+ });
11
+
12
+ export type EnforceToolCallRequest = z.infer<typeof enforceToolCallRequestSchema>;
13
+
14
+ export type ToolCallFinding = {
15
+ id: string;
16
+ label: string;
17
+ field?: string;
18
+ };
19
+
20
+ export type EnforceToolCallResult = {
21
+ action: EnforcementAction;
22
+ maskedArguments: Record<string, unknown>;
23
+ findings: ToolCallFinding[];
24
+ entitiesMasked: number;
25
+ riskTier: "low" | "medium" | "high";
26
+ requestId: string;
27
+ };
package/src/events.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+
3
+ export const eventIngestSchema = z.object({
4
+ platform: z.string().max(40).optional(),
5
+ kind: z.string().max(48).optional(),
6
+ entitiesMasked: z.number().int().min(0).max(5000).optional(),
7
+ riskTier: z.enum(["low", "medium", "high"]).optional(),
8
+ sourceHost: z.string().max(253).optional(),
9
+ action: z.string().max(48).optional(),
10
+ maskProvider: z.enum(["azure", "fallback_local"]).optional(),
11
+ maskPhase: z.string().max(48).optional(),
12
+ clientSurface: z.enum(["cursor", "claude-desktop", "codex", "sdk", "extension", "unknown"]).optional(),
13
+ modelProvider: z.enum(["anthropic", "openai", "google", "xai", "cursor", "unknown"]).optional(),
14
+ modelId: z.string().max(80).optional(),
15
+ mcpToolName: z.string().max(120).optional(),
16
+ downstreamServer: z.string().max(80).optional(),
17
+ });
18
+
19
+ export type EventIngest = z.infer<typeof eventIngestSchema>;
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./mcp-policy.js";
2
+ export * from "./auth.js";
3
+ export * from "./mask.js";
4
+ export * from "./events.js";
5
+ export * from "./policy.js";
6
+ export * from "./enforcement.js";
7
+ export * from "./enforce-local.js";
8
+ export * from "./secret-patterns.js";
package/src/mask.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+
3
+ export const maskRequestSchema = z.object({
4
+ text: z.string().min(1),
5
+ platform: z.string().max(40).optional(),
6
+ });
7
+
8
+ export type MaskRequest = z.infer<typeof maskRequestSchema>;
9
+
10
+ export type WireRedactionAction = "mask_and_send" | "mask_and_confirm" | "block_and_review";
11
+
12
+ export type MaskResponse = {
13
+ maskedText: string;
14
+ tokenMap: Record<string, string>;
15
+ entitiesMasked: number;
16
+ platform: string;
17
+ provider: "azure" | "fallback_local";
18
+ requestId: string;
19
+ piiLanguage?: string;
20
+ decision?: WireRedactionAction;
21
+ blocked?: boolean;
22
+ blockReason?: string;
23
+ };
24
+
25
+ export type MaskErrorCode =
26
+ | "billing_required"
27
+ | "credits_exhausted"
28
+ | "prompt_limit_exceeded"
29
+ | "mask_fallback_blocked"
30
+ | "high_risk_literal_remaining"
31
+ | "plan_upgrade_required";
@@ -0,0 +1,47 @@
1
+ export type McpToolPolicyAction = "allow" | "mask" | "block";
2
+
3
+ export type McpToolPolicy = {
4
+ allowedTools: string[];
5
+ deniedTools: string[];
6
+ defaultAction: McpToolPolicyAction;
7
+ };
8
+
9
+ export const DEFAULT_MCP_TOOL_POLICY: McpToolPolicy = {
10
+ allowedTools: [],
11
+ deniedTools: [],
12
+ defaultAction: "mask",
13
+ };
14
+
15
+ export function normalizeMcpToolPolicy(raw: unknown): McpToolPolicy {
16
+ if (!raw || typeof raw !== "object") return { ...DEFAULT_MCP_TOOL_POLICY };
17
+ const o = raw as Record<string, unknown>;
18
+ const allowedTools = Array.isArray(o.allowedTools)
19
+ ? o.allowedTools.filter((t): t is string => typeof t === "string").map((t) => t.trim()).filter(Boolean).slice(0, 64)
20
+ : [];
21
+ const deniedTools = Array.isArray(o.deniedTools)
22
+ ? o.deniedTools.filter((t): t is string => typeof t === "string").map((t) => t.trim()).filter(Boolean).slice(0, 64)
23
+ : [];
24
+ const defaultAction =
25
+ o.defaultAction === "allow" || o.defaultAction === "mask" || o.defaultAction === "block"
26
+ ? o.defaultAction
27
+ : "mask";
28
+ return { allowedTools, deniedTools, defaultAction };
29
+ }
30
+
31
+ /** Resolve workspace MCP tool policy before local/cloud enforcement. */
32
+ export function resolveMcpToolPolicyDecision(
33
+ policy: McpToolPolicy,
34
+ toolName: string,
35
+ localAction: McpToolPolicyAction
36
+ ): McpToolPolicyAction {
37
+ const name = toolName.trim().toLowerCase();
38
+ if (policy.deniedTools.some((t) => t.toLowerCase() === name)) return "block";
39
+ if (policy.allowedTools.length > 0) {
40
+ const allowed = policy.allowedTools.some((t) => t.toLowerCase() === name);
41
+ if (!allowed) return "block";
42
+ }
43
+ if (localAction === "block") return "block";
44
+ if (policy.defaultAction === "block") return "block";
45
+ if (localAction === "mask") return "mask";
46
+ return policy.defaultAction;
47
+ }
package/src/policy.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { WireRedactionAction } from "./mask.js";
2
+
3
+ export type PromptRiskTier = "low" | "medium" | "high";
4
+
5
+ export type PolicySimulation = {
6
+ platform: string;
7
+ risk: PromptRiskTier;
8
+ action: WireRedactionAction;
9
+ matchedKeywords: string[];
10
+ shieldEnabled: boolean;
11
+ };
12
+
13
+ export type PolicySimulateRequest = {
14
+ prompt: string;
15
+ platform?: string;
16
+ sourceHost?: string;
17
+ };
@@ -0,0 +1,43 @@
1
+ /** Portable secret/PII patterns for local fast scan (aligned with webapp detectors). */
2
+
3
+ export type SecretPattern = { id: string; label: string; pattern: RegExp };
4
+
5
+ export function buildSecretPatterns(): SecretPattern[] {
6
+ return [
7
+ { id: "aws_access_key", label: "AWS_KEY", pattern: /\b(?:AKIA|ASIA|AIDA|AROA|AGPA|AIPA|ANPA|ANVA|APKA|ASCA|ACCA)[A-Z0-9]{16}\b/g },
8
+ { id: "github_pat", label: "GITHUB", pattern: /\bgh[oprus]_[A-Za-z0-9_]{36,255}\b/g },
9
+ { id: "github_pat_fine", label: "GITHUB_FINE", pattern: /\bgithub_pat_[A-Za-z0-9_]{80,500}\b/g },
10
+ { id: "openai_sk", label: "OPENAI", pattern: /\bsk-[A-Za-z0-9]{20,}\b/g },
11
+ { id: "openai_sk_proj", label: "OPENAI_PROJ", pattern: /\bsk-proj-[A-Za-z0-9_-]{20,500}\b/g },
12
+ { id: "stripe_sk", label: "STRIPE", pattern: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,128}\b/g },
13
+ { id: "slack_token", label: "SLACK", pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,128}\b/g },
14
+ { id: "google_api_key", label: "GOOGLE_KEY", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
15
+ { id: "jwt", label: "JWT", pattern: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g },
16
+ { id: "email", label: "EMAIL", pattern: /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi },
17
+ { id: "ssn", label: "SSN", pattern: /\b\d{3}-\d{2}-\d{4}\b/g },
18
+ { id: "iban", label: "IBAN", pattern: /\b[A-Z]{2}\d{2}(?:[ -]?[A-Z0-9]){11,30}\b/gi },
19
+ { id: "card", label: "CARD", pattern: /\b(?:\d[ -]?){12,19}\b/g },
20
+ ];
21
+ }
22
+
23
+ export type ScanFinding = {
24
+ id: string;
25
+ label: string;
26
+ match: string;
27
+ start: number;
28
+ end: number;
29
+ };
30
+
31
+ export function scanTextForSecrets(text: string): ScanFinding[] {
32
+ const findings: ScanFinding[] = [];
33
+ for (const { id, label, pattern } of buildSecretPatterns()) {
34
+ const re = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`);
35
+ let m: RegExpExecArray | null;
36
+ while ((m = re.exec(text)) !== null) {
37
+ const match = m[0];
38
+ if (match.includes("[") && match.includes("]")) continue;
39
+ findings.push({ id, label, match, start: m.index, end: m.index + match.length });
40
+ }
41
+ }
42
+ return findings;
43
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "outDir": "dist",
9
+ "rootDir": "src",
10
+ "strict": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": ["src/**/*"]
14
+ }