pi-harness-runtime 1.1.164 → 1.1.166
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/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.d.ts.map +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.js +13 -0
- package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
- package/packages/notification/dist/index.d.ts +1 -0
- package/packages/notification/dist/index.d.ts.map +1 -1
- package/packages/notification/dist/index.js +1 -0
- package/packages/notification/dist/index.js.map +1 -1
- package/packages/notification/dist/telegram-content-filter.d.ts +36 -0
- package/packages/notification/dist/telegram-content-filter.d.ts.map +1 -0
- package/packages/notification/dist/telegram-content-filter.js +176 -0
- package/packages/notification/dist/telegram-content-filter.js.map +1 -0
- package/packages/notification/package.json +1 -1
- package/packages/notification/src/adapters/telegram-adapter.ts +16 -0
- package/packages/notification/src/index.ts +4 -0
- package/packages/notification/src/telegram-content-filter.ts +225 -0
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Content Filter — Jev-Powered Sensitive Content Detection
|
|
3
|
+
*
|
|
4
|
+
* Before sending to Telegram, this module analyzes content for sensitive
|
|
5
|
+
* credentials that should NOT be transmitted.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
/**
|
|
11
|
+
* Get API key from key file
|
|
12
|
+
*/
|
|
13
|
+
function getKeyFromFile(keyName) {
|
|
14
|
+
const keyPath = join(homedir(), ".pi-harness-runtime", "keys", `${keyName}.txt`);
|
|
15
|
+
if (existsSync(keyPath)) {
|
|
16
|
+
try {
|
|
17
|
+
return readFileSync(keyPath, "utf8").trim();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Extract text content from notification payload
|
|
27
|
+
*/
|
|
28
|
+
function extractContent(payload) {
|
|
29
|
+
const parts = [];
|
|
30
|
+
if (payload.title)
|
|
31
|
+
parts.push(payload.title);
|
|
32
|
+
if (payload.message)
|
|
33
|
+
parts.push(payload.message);
|
|
34
|
+
if (payload.details) {
|
|
35
|
+
for (const [_key, value] of Object.entries(payload.details)) {
|
|
36
|
+
if (typeof value === "string" && value) {
|
|
37
|
+
parts.push(value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return parts.join("\n");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Simple heuristic check for obvious credentials (fallback when Jev unavailable)
|
|
45
|
+
*/
|
|
46
|
+
function heuristicCheck(content) {
|
|
47
|
+
// Check for common credential patterns
|
|
48
|
+
const sensitivePatterns = [
|
|
49
|
+
/\d+:[-A-Za-z0-9_]{35,}/, // Telegram bot token
|
|
50
|
+
/sk-[a-zA-Z0-9]{20,}/, // OpenAI-style keys
|
|
51
|
+
/[a-zA-Z0-9_-]{32,}/, // Long API keys
|
|
52
|
+
/Bearer\s+[a-zA-Z0-9_-]{20,}/i, // Bearer tokens
|
|
53
|
+
/(api[_-]?key|secret|token|password|credential)[=:\s]+[^\s]{16,}/i,
|
|
54
|
+
/-----BEGIN\s+(RSA|EC|OPENSSH|DSA)\s+PRIVATE\s+KEY-----/,
|
|
55
|
+
/infisical[_-]?(client[_-])?(secret|id)/i,
|
|
56
|
+
];
|
|
57
|
+
for (const pattern of sensitivePatterns) {
|
|
58
|
+
if (pattern.test(content)) {
|
|
59
|
+
let matchType = "api_key_or_secret";
|
|
60
|
+
if (/\d+:[-A-Za-z0-9_]{35,}/.test(content)) {
|
|
61
|
+
matchType = "telegram_bot_token";
|
|
62
|
+
}
|
|
63
|
+
else if (/sk-[a-zA-Z0-9]{20,}/.test(content)) {
|
|
64
|
+
matchType = "openai_style_key";
|
|
65
|
+
}
|
|
66
|
+
else if (/-----BEGIN\s+(RSA|EC|OPENSSH|DSA)\s+PRIVATE\s+KEY-----/.test(content)) {
|
|
67
|
+
matchType = "private_key";
|
|
68
|
+
}
|
|
69
|
+
else if (/infisical/i.test(content)) {
|
|
70
|
+
matchType = "infisical_credential";
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
shouldSend: false,
|
|
74
|
+
judgment: "BLOCK",
|
|
75
|
+
reason: `Sensitive content detected: ${matchType}`,
|
|
76
|
+
confidence: 0.95,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
shouldSend: true,
|
|
82
|
+
judgment: "SEND",
|
|
83
|
+
confidence: 0.9,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Call Jev to analyze content for sensitive credentials
|
|
88
|
+
*/
|
|
89
|
+
async function callJev(content) {
|
|
90
|
+
// Get API key from environment or key file
|
|
91
|
+
const apiKey = process.env.TYPESAFE_API_KEY || getKeyFromFile("jev-api-key") || getKeyFromFile("typesafe-api-key");
|
|
92
|
+
if (!apiKey) {
|
|
93
|
+
return null; // Jev not available
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const response = await fetch("https://api.typesafe.ai/v1/jev/decide", {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: {
|
|
99
|
+
"Content-Type": "application/json",
|
|
100
|
+
Authorization: `Bearer ${apiKey}`,
|
|
101
|
+
},
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
question: "Does this message contain any API keys, tokens, secrets, passwords, or other sensitive credentials that should NOT be sent to a third-party service like Telegram? Answer only YES or NO.",
|
|
104
|
+
context: {
|
|
105
|
+
message: content,
|
|
106
|
+
action: "telegram_notification",
|
|
107
|
+
security_level: "high",
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const data = (await response.json());
|
|
115
|
+
const decision = data.decision?.toUpperCase() ?? "UNKNOWN";
|
|
116
|
+
const isSensitive = decision === "YES" || decision === "BLOCK";
|
|
117
|
+
return {
|
|
118
|
+
shouldSend: !isSensitive,
|
|
119
|
+
judgment: isSensitive ? "BLOCK" : "SEND",
|
|
120
|
+
reason: data.reasoning,
|
|
121
|
+
confidence: data.confidence,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Filter notification content before sending to Telegram
|
|
130
|
+
*
|
|
131
|
+
* @param payload - The notification payload
|
|
132
|
+
* @returns Filter result with decision
|
|
133
|
+
*/
|
|
134
|
+
export async function filterTelegramContent(payload) {
|
|
135
|
+
const content = extractContent(payload);
|
|
136
|
+
// First, run heuristic check (always available)
|
|
137
|
+
const heuristicResult = heuristicCheck(content);
|
|
138
|
+
if (heuristicResult.judgment === "BLOCK") {
|
|
139
|
+
logFilterDecision("BLOCK", heuristicResult.reason ?? "Heuristic match", payload.jobId);
|
|
140
|
+
return heuristicResult;
|
|
141
|
+
}
|
|
142
|
+
// Try Jev if available
|
|
143
|
+
const jevResult = await callJev(content);
|
|
144
|
+
if (jevResult) {
|
|
145
|
+
if (jevResult.judgment === "BLOCK") {
|
|
146
|
+
logFilterDecision("BLOCK", jevResult.reason ?? "Jev judgment", payload.jobId);
|
|
147
|
+
}
|
|
148
|
+
return jevResult;
|
|
149
|
+
}
|
|
150
|
+
// Jev unavailable - use heuristic result
|
|
151
|
+
return heuristicResult;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Log filter decision securely (no content, just metadata)
|
|
155
|
+
*/
|
|
156
|
+
function logFilterDecision(decision, reason, jobId) {
|
|
157
|
+
console.warn(`[TelegramFilter] ${decision} job=${jobId} reason=${reason} channel=telegram`);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Mask a value safely for logging
|
|
161
|
+
*/
|
|
162
|
+
function maskValue(value) {
|
|
163
|
+
if (!value || value.length <= 8)
|
|
164
|
+
return "****";
|
|
165
|
+
return `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get safe notification version (masked) for logging
|
|
169
|
+
*/
|
|
170
|
+
export function getSafeNotificationLog(payload) {
|
|
171
|
+
const maskedTitle = maskValue(payload.title);
|
|
172
|
+
const maskedMessage = maskValue(payload.message);
|
|
173
|
+
const maskedJobId = maskValue(payload.jobId);
|
|
174
|
+
return `event=${payload.event} job=${maskedJobId} title=${maskedTitle} message_len=${maskedMessage.length}`;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=telegram-content-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telegram-content-filter.js","sourceRoot":"","sources":["../src/telegram-content-filter.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
} from "../types.js";
|
|
20
20
|
import { BaseChannelAdapter } from "../base-adapter.js";
|
|
21
21
|
import { maskString } from "../mask-secrets.js";
|
|
22
|
+
import { filterTelegramContent, getSafeNotificationLog } from "../telegram-content-filter.js";
|
|
22
23
|
|
|
23
24
|
export class TelegramAdapter extends BaseChannelAdapter {
|
|
24
25
|
readonly id = "telegram";
|
|
@@ -71,6 +72,21 @@ return this._botUsername;
|
|
|
71
72
|
*/
|
|
72
73
|
async send(payload: NotificationPayload): Promise<NotificationResult> {
|
|
73
74
|
try {
|
|
75
|
+
// SECURITY: Jev-powered content filter - block if contains sensitive credentials
|
|
76
|
+
const filterResult = await filterTelegramContent(payload);
|
|
77
|
+
if (!filterResult.shouldSend) {
|
|
78
|
+
console.warn(
|
|
79
|
+
`[TelegramAdapter] BLOCKED by Jev judgment=${filterResult.judgment} reason=${filterResult.reason} job=${payload.jobId}`,
|
|
80
|
+
);
|
|
81
|
+
// Log safe notification metadata (no secrets)
|
|
82
|
+
console.log(`[TelegramAdapter] ${getSafeNotificationLog(payload)}`);
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
channel: this.id,
|
|
86
|
+
error: "Content blocked: sensitive credentials detected",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
74
90
|
const cfg = this.config.config as TelegramConfig;
|
|
75
91
|
const message = this.formatMessage(payload);
|
|
76
92
|
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Content Filter — Jev-Powered Sensitive Content Detection
|
|
3
|
+
*
|
|
4
|
+
* Before sending to Telegram, this module analyzes content for sensitive
|
|
5
|
+
* credentials that should NOT be transmitted.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { NotificationPayload } from "./types.js";
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get API key from key file
|
|
15
|
+
*/
|
|
16
|
+
function getKeyFromFile(keyName: string): string | undefined {
|
|
17
|
+
const keyPath = join(homedir(), ".pi-harness-runtime", "keys", `${keyName}.txt`);
|
|
18
|
+
if (existsSync(keyPath)) {
|
|
19
|
+
try {
|
|
20
|
+
return readFileSync(keyPath, "utf8").trim();
|
|
21
|
+
} catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Jev judgment result for content filtering
|
|
30
|
+
*/
|
|
31
|
+
export type ContentJudgment = "SEND" | "BLOCK" | "UNKNOWN";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Result of content filtering decision
|
|
35
|
+
*/
|
|
36
|
+
export interface FilterResult {
|
|
37
|
+
/** Whether to send to Telegram */
|
|
38
|
+
shouldSend: boolean;
|
|
39
|
+
/** Jev's judgment */
|
|
40
|
+
judgment: ContentJudgment;
|
|
41
|
+
/** Reason for blocking (if blocked) */
|
|
42
|
+
reason?: string;
|
|
43
|
+
/** Confidence score (0-1) */
|
|
44
|
+
confidence?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Extract text content from notification payload
|
|
49
|
+
*/
|
|
50
|
+
function extractContent(payload: NotificationPayload): string {
|
|
51
|
+
const parts: string[] = [];
|
|
52
|
+
|
|
53
|
+
if (payload.title) parts.push(payload.title);
|
|
54
|
+
if (payload.message) parts.push(payload.message);
|
|
55
|
+
|
|
56
|
+
if (payload.details) {
|
|
57
|
+
for (const [_key, value] of Object.entries(payload.details)) {
|
|
58
|
+
if (typeof value === "string" && value) {
|
|
59
|
+
parts.push(value);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return parts.join("\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Simple heuristic check for obvious credentials (fallback when Jev unavailable)
|
|
69
|
+
*/
|
|
70
|
+
function heuristicCheck(content: string): FilterResult {
|
|
71
|
+
// Check for common credential patterns
|
|
72
|
+
const sensitivePatterns = [
|
|
73
|
+
/\d+:[-A-Za-z0-9_]{35,}/, // Telegram bot token
|
|
74
|
+
/sk-[a-zA-Z0-9]{20,}/, // OpenAI-style keys
|
|
75
|
+
/[a-zA-Z0-9_-]{32,}/, // Long API keys
|
|
76
|
+
/Bearer\s+[a-zA-Z0-9_-]{20,}/i, // Bearer tokens
|
|
77
|
+
/(api[_-]?key|secret|token|password|credential)[=:\s]+[^\s]{16,}/i,
|
|
78
|
+
/-----BEGIN\s+(RSA|EC|OPENSSH|DSA)\s+PRIVATE\s+KEY-----/,
|
|
79
|
+
/infisical[_-]?(client[_-])?(secret|id)/i,
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
for (const pattern of sensitivePatterns) {
|
|
83
|
+
if (pattern.test(content)) {
|
|
84
|
+
let matchType = "api_key_or_secret";
|
|
85
|
+
if (/\d+:[-A-Za-z0-9_]{35,}/.test(content)) {
|
|
86
|
+
matchType = "telegram_bot_token";
|
|
87
|
+
} else if (/sk-[a-zA-Z0-9]{20,}/.test(content)) {
|
|
88
|
+
matchType = "openai_style_key";
|
|
89
|
+
} else if (/-----BEGIN\s+(RSA|EC|OPENSSH|DSA)\s+PRIVATE\s+KEY-----/.test(content)) {
|
|
90
|
+
matchType = "private_key";
|
|
91
|
+
} else if (/infisical/i.test(content)) {
|
|
92
|
+
matchType = "infisical_credential";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
shouldSend: false,
|
|
97
|
+
judgment: "BLOCK",
|
|
98
|
+
reason: `Sensitive content detected: ${matchType}`,
|
|
99
|
+
confidence: 0.95,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
shouldSend: true,
|
|
106
|
+
judgment: "SEND",
|
|
107
|
+
confidence: 0.9,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Call Jev to analyze content for sensitive credentials
|
|
113
|
+
*/
|
|
114
|
+
async function callJev(content: string): Promise<FilterResult | null> {
|
|
115
|
+
// Get API key from environment or key file
|
|
116
|
+
const apiKey = process.env.TYPESAFE_API_KEY || getKeyFromFile("jev-api-key") || getKeyFromFile("typesafe-api-key");
|
|
117
|
+
|
|
118
|
+
if (!apiKey) {
|
|
119
|
+
return null; // Jev not available
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const response = await fetch("https://api.typesafe.ai/v1/jev/decide", {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: {
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
Authorization: `Bearer ${apiKey}`,
|
|
128
|
+
},
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
question:
|
|
131
|
+
"Does this message contain any API keys, tokens, secrets, passwords, or other sensitive credentials that should NOT be sent to a third-party service like Telegram? Answer only YES or NO.",
|
|
132
|
+
context: {
|
|
133
|
+
message: content,
|
|
134
|
+
action: "telegram_notification",
|
|
135
|
+
security_level: "high",
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (!response.ok) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const data = (await response.json()) as {
|
|
145
|
+
decision?: string;
|
|
146
|
+
confidence?: number;
|
|
147
|
+
reasoning?: string;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const decision = data.decision?.toUpperCase() ?? "UNKNOWN";
|
|
151
|
+
const isSensitive = decision === "YES" || decision === "BLOCK";
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
shouldSend: !isSensitive,
|
|
155
|
+
judgment: isSensitive ? "BLOCK" : "SEND",
|
|
156
|
+
reason: data.reasoning,
|
|
157
|
+
confidence: data.confidence,
|
|
158
|
+
};
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Filter notification content before sending to Telegram
|
|
166
|
+
*
|
|
167
|
+
* @param payload - The notification payload
|
|
168
|
+
* @returns Filter result with decision
|
|
169
|
+
*/
|
|
170
|
+
export async function filterTelegramContent(
|
|
171
|
+
payload: NotificationPayload,
|
|
172
|
+
): Promise<FilterResult> {
|
|
173
|
+
const content = extractContent(payload);
|
|
174
|
+
|
|
175
|
+
// First, run heuristic check (always available)
|
|
176
|
+
const heuristicResult = heuristicCheck(content);
|
|
177
|
+
if (heuristicResult.judgment === "BLOCK") {
|
|
178
|
+
logFilterDecision("BLOCK", heuristicResult.reason ?? "Heuristic match", payload.jobId);
|
|
179
|
+
return heuristicResult;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Try Jev if available
|
|
183
|
+
const jevResult = await callJev(content);
|
|
184
|
+
if (jevResult) {
|
|
185
|
+
if (jevResult.judgment === "BLOCK") {
|
|
186
|
+
logFilterDecision("BLOCK", jevResult.reason ?? "Jev judgment", payload.jobId);
|
|
187
|
+
}
|
|
188
|
+
return jevResult;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Jev unavailable - use heuristic result
|
|
192
|
+
return heuristicResult;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Log filter decision securely (no content, just metadata)
|
|
197
|
+
*/
|
|
198
|
+
function logFilterDecision(
|
|
199
|
+
decision: ContentJudgment,
|
|
200
|
+
reason: string,
|
|
201
|
+
jobId: string,
|
|
202
|
+
): void {
|
|
203
|
+
console.warn(
|
|
204
|
+
`[TelegramFilter] ${decision} job=${jobId} reason=${reason} channel=telegram`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Mask a value safely for logging
|
|
210
|
+
*/
|
|
211
|
+
function maskValue(value: string): string {
|
|
212
|
+
if (!value || value.length <= 8) return "****";
|
|
213
|
+
return `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get safe notification version (masked) for logging
|
|
218
|
+
*/
|
|
219
|
+
export function getSafeNotificationLog(payload: NotificationPayload): string {
|
|
220
|
+
const maskedTitle = maskValue(payload.title);
|
|
221
|
+
const maskedMessage = maskValue(payload.message);
|
|
222
|
+
const maskedJobId = maskValue(payload.jobId);
|
|
223
|
+
|
|
224
|
+
return `event=${payload.event} job=${maskedJobId} title=${maskedTitle} message_len=${maskedMessage.length}`;
|
|
225
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/provider-router",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.166",
|
|
4
4
|
"description": "Enhanced Provider Router for pi-harness-runtime — intelligent routing with capabilities, costs, and quotas",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/provider-router.js",
|