rird 1.0.200
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/AGENTS.md +27 -0
- package/Dockerfile +18 -0
- package/README.md +15 -0
- package/bin/opencode +336 -0
- package/bin/pty-wrapper.js +285 -0
- package/bunfig.toml +4 -0
- package/facebook_ads_library.png +0 -0
- package/nul`nif +0 -0
- package/package.json +111 -0
- package/parsers-config.ts +239 -0
- package/rird-1.0.199.tgz +0 -0
- package/script/build-windows.ts +54 -0
- package/script/build.ts +167 -0
- package/script/postinstall.mjs +544 -0
- package/script/publish-registries.ts +187 -0
- package/script/publish.ts +72 -0
- package/script/schema.ts +47 -0
- package/src/acp/README.md +164 -0
- package/src/acp/agent.ts +1063 -0
- package/src/acp/session.ts +101 -0
- package/src/acp/types.ts +22 -0
- package/src/agent/agent.ts +367 -0
- package/src/agent/generate.txt +75 -0
- package/src/agent/prompt/compaction.txt +12 -0
- package/src/agent/prompt/explore.txt +18 -0
- package/src/agent/prompt/summary.txt +10 -0
- package/src/agent/prompt/title.txt +36 -0
- package/src/auth/index.ts +70 -0
- package/src/bun/index.ts +114 -0
- package/src/bus/bus-event.ts +43 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +105 -0
- package/src/cli/bootstrap.ts +17 -0
- package/src/cli/cmd/acp.ts +88 -0
- package/src/cli/cmd/agent.ts +256 -0
- package/src/cli/cmd/auth.ts +391 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/debug/config.ts +15 -0
- package/src/cli/cmd/debug/file.ts +91 -0
- package/src/cli/cmd/debug/index.ts +43 -0
- package/src/cli/cmd/debug/lsp.ts +48 -0
- package/src/cli/cmd/debug/ripgrep.ts +83 -0
- package/src/cli/cmd/debug/scrap.ts +15 -0
- package/src/cli/cmd/debug/skill.ts +15 -0
- package/src/cli/cmd/debug/snapshot.ts +48 -0
- package/src/cli/cmd/export.ts +88 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +1400 -0
- package/src/cli/cmd/import.ts +98 -0
- package/src/cli/cmd/mcp.ts +654 -0
- package/src/cli/cmd/models.ts +77 -0
- package/src/cli/cmd/pr.ts +112 -0
- package/src/cli/cmd/run.ts +368 -0
- package/src/cli/cmd/serve.ts +31 -0
- package/src/cli/cmd/session.ts +106 -0
- package/src/cli/cmd/stats.ts +298 -0
- package/src/cli/cmd/tui/app.tsx +696 -0
- package/src/cli/cmd/tui/attach.ts +30 -0
- package/src/cli/cmd/tui/component/border.tsx +21 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +245 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +224 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +102 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-stash.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
- package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/did-you-know.tsx +85 -0
- package/src/cli/cmd/tui/component/logo.tsx +35 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +574 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +1090 -0
- package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
- package/src/cli/cmd/tui/component/tips.ts +27 -0
- package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
- package/src/cli/cmd/tui/context/args.tsx +14 -0
- package/src/cli/cmd/tui/context/directory.ts +13 -0
- package/src/cli/cmd/tui/context/exit.tsx +23 -0
- package/src/cli/cmd/tui/context/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/keybind.tsx +101 -0
- package/src/cli/cmd/tui/context/kv.tsx +49 -0
- package/src/cli/cmd/tui/context/local.tsx +354 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +46 -0
- package/src/cli/cmd/tui/context/sdk.tsx +74 -0
- package/src/cli/cmd/tui/context/sync.tsx +372 -0
- package/src/cli/cmd/tui/context/theme/aura.json +69 -0
- package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
- package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
- package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
- package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
- package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
- package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
- package/src/cli/cmd/tui/context/theme/github.json +233 -0
- package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
- package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
- package/src/cli/cmd/tui/context/theme/lucent-orng.json +227 -0
- package/src/cli/cmd/tui/context/theme/material.json +235 -0
- package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
- package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
- package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
- package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
- package/src/cli/cmd/tui/context/theme/nord.json +223 -0
- package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
- package/src/cli/cmd/tui/context/theme/orng.json +245 -0
- package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
- package/src/cli/cmd/tui/context/theme/rird.json +245 -0
- package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
- package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
- package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
- package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
- package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
- package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
- package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
- package/src/cli/cmd/tui/context/theme.tsx +1109 -0
- package/src/cli/cmd/tui/event.ts +40 -0
- package/src/cli/cmd/tui/routes/home.tsx +138 -0
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
- package/src/cli/cmd/tui/routes/session/header.tsx +125 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +1864 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +318 -0
- package/src/cli/cmd/tui/spawn.ts +60 -0
- package/src/cli/cmd/tui/thread.ts +142 -0
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
- package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +332 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
- package/src/cli/cmd/tui/ui/spinner.ts +368 -0
- package/src/cli/cmd/tui/ui/toast.tsx +100 -0
- package/src/cli/cmd/tui/util/clipboard.ts +127 -0
- package/src/cli/cmd/tui/util/editor.ts +32 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/worker.ts +63 -0
- package/src/cli/cmd/uninstall.ts +344 -0
- package/src/cli/cmd/upgrade.ts +100 -0
- package/src/cli/cmd/web.ts +84 -0
- package/src/cli/error.ts +56 -0
- package/src/cli/ui.ts +84 -0
- package/src/cli/upgrade.ts +25 -0
- package/src/command/index.ts +80 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/command/template/review.txt +97 -0
- package/src/config/config.ts +995 -0
- package/src/config/markdown.ts +41 -0
- package/src/env/index.ts +26 -0
- package/src/file/ignore.ts +83 -0
- package/src/file/index.ts +328 -0
- package/src/file/ripgrep.ts +393 -0
- package/src/file/time.ts +64 -0
- package/src/file/watcher.ts +103 -0
- package/src/flag/flag.ts +46 -0
- package/src/format/formatter.ts +315 -0
- package/src/format/index.ts +137 -0
- package/src/global/index.ts +52 -0
- package/src/id/id.ts +73 -0
- package/src/ide/index.ts +76 -0
- package/src/index.ts +240 -0
- package/src/installation/index.ts +239 -0
- package/src/lsp/client.ts +229 -0
- package/src/lsp/index.ts +485 -0
- package/src/lsp/language.ts +116 -0
- package/src/lsp/server.ts +1895 -0
- package/src/mcp/auth.ts +135 -0
- package/src/mcp/index.ts +690 -0
- package/src/mcp/oauth-callback.ts +200 -0
- package/src/mcp/oauth-provider.ts +154 -0
- package/src/patch/index.ts +622 -0
- package/src/permission/index.ts +199 -0
- package/src/plugin/index.ts +91 -0
- package/src/project/bootstrap.ts +31 -0
- package/src/project/instance.ts +78 -0
- package/src/project/project.ts +221 -0
- package/src/project/state.ts +65 -0
- package/src/project/vcs.ts +76 -0
- package/src/provider/auth.ts +143 -0
- package/src/provider/models-macro.ts +11 -0
- package/src/provider/models.ts +106 -0
- package/src/provider/provider.ts +1071 -0
- package/src/provider/sdk/openai-compatible/src/README.md +5 -0
- package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
- package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
- package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
- package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
- package/src/provider/transform.ts +455 -0
- package/src/pty/index.ts +231 -0
- package/src/security/guardrails.test.ts +341 -0
- package/src/security/guardrails.ts +558 -0
- package/src/security/index.ts +19 -0
- package/src/server/error.ts +36 -0
- package/src/server/project.ts +79 -0
- package/src/server/server.ts +2642 -0
- package/src/server/tui.ts +71 -0
- package/src/session/compaction.ts +223 -0
- package/src/session/index.ts +461 -0
- package/src/session/llm.ts +201 -0
- package/src/session/message-v2.ts +690 -0
- package/src/session/message.ts +189 -0
- package/src/session/processor.ts +409 -0
- package/src/session/prompt/act-switch.txt +5 -0
- package/src/session/prompt/anthropic-20250930.txt +166 -0
- package/src/session/prompt/anthropic.txt +85 -0
- package/src/session/prompt/anthropic_spoof.txt +1 -0
- package/src/session/prompt/beast.txt +103 -0
- package/src/session/prompt/codex.txt +304 -0
- package/src/session/prompt/copilot-gpt-5.txt +138 -0
- package/src/session/prompt/gemini.txt +85 -0
- package/src/session/prompt/max-steps.txt +16 -0
- package/src/session/prompt/plan-reminder-anthropic.txt +35 -0
- package/src/session/prompt/plan.txt +24 -0
- package/src/session/prompt/polaris.txt +84 -0
- package/src/session/prompt/qwen.txt +106 -0
- package/src/session/prompt.ts +1509 -0
- package/src/session/retry.ts +86 -0
- package/src/session/revert.ts +108 -0
- package/src/session/sensitive-filter.test.ts +327 -0
- package/src/session/sensitive-filter.ts +466 -0
- package/src/session/status.ts +76 -0
- package/src/session/summary.ts +194 -0
- package/src/session/system.ts +120 -0
- package/src/session/todo.ts +37 -0
- package/src/share/share-next.ts +194 -0
- package/src/share/share.ts +87 -0
- package/src/shell/shell.ts +67 -0
- package/src/skill/index.ts +1 -0
- package/src/skill/skill.ts +83 -0
- package/src/snapshot/index.ts +197 -0
- package/src/storage/storage.ts +226 -0
- package/src/tests/agent.test.ts +308 -0
- package/src/tests/build-guards.test.ts +267 -0
- package/src/tests/config.test.ts +664 -0
- package/src/tests/tool-registry.test.ts +589 -0
- package/src/tool/bash.ts +317 -0
- package/src/tool/bash.txt +158 -0
- package/src/tool/batch.ts +175 -0
- package/src/tool/batch.txt +24 -0
- package/src/tool/codesearch.ts +168 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/edit.ts +675 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/glob.ts +65 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +121 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +110 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp-diagnostics.ts +26 -0
- package/src/tool/lsp-diagnostics.txt +1 -0
- package/src/tool/lsp-hover.ts +31 -0
- package/src/tool/lsp-hover.txt +1 -0
- package/src/tool/lsp.ts +87 -0
- package/src/tool/lsp.txt +19 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/patch.ts +233 -0
- package/src/tool/patch.txt +1 -0
- package/src/tool/read.ts +219 -0
- package/src/tool/read.txt +12 -0
- package/src/tool/registry.ts +162 -0
- package/src/tool/skill.ts +100 -0
- package/src/tool/task.ts +136 -0
- package/src/tool/task.txt +51 -0
- package/src/tool/todo.ts +39 -0
- package/src/tool/todoread.txt +14 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +71 -0
- package/src/tool/webfetch.ts +198 -0
- package/src/tool/webfetch.txt +13 -0
- package/src/tool/websearch.ts +180 -0
- package/src/tool/websearch.txt +11 -0
- package/src/tool/write.ts +110 -0
- package/src/tool/write.txt +8 -0
- package/src/util/archive.ts +16 -0
- package/src/util/color.ts +19 -0
- package/src/util/context.ts +25 -0
- package/src/util/defer.ts +12 -0
- package/src/util/eventloop.ts +20 -0
- package/src/util/filesystem.ts +83 -0
- package/src/util/fn.ts +11 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +102 -0
- package/src/util/lazy.ts +11 -0
- package/src/util/license.ts +325 -0
- package/src/util/locale.ts +81 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log.ts +180 -0
- package/src/util/queue.ts +32 -0
- package/src/util/rpc.ts +42 -0
- package/src/util/scrap.ts +10 -0
- package/src/util/signal.ts +12 -0
- package/src/util/timeout.ts +14 -0
- package/src/util/token.ts +7 -0
- package/src/util/wildcard.ts +54 -0
- package/sst-env.d.ts +9 -0
- package/test/agent/agent.test.ts +146 -0
- package/test/bun.test.ts +53 -0
- package/test/cli/github-remote.test.ts +80 -0
- package/test/config/agent-color.test.ts +66 -0
- package/test/config/config.test.ts +535 -0
- package/test/config/markdown.test.ts +89 -0
- package/test/file/ignore.test.ts +10 -0
- package/test/fixture/fixture.ts +36 -0
- package/test/fixture/lsp/fake-lsp-server.js +77 -0
- package/test/ide/ide.test.ts +82 -0
- package/test/keybind.test.ts +421 -0
- package/test/lsp/client.test.ts +95 -0
- package/test/mcp/headers.test.ts +153 -0
- package/test/patch/patch.test.ts +348 -0
- package/test/preload.ts +57 -0
- package/test/project/project.test.ts +72 -0
- package/test/provider/provider.test.ts +1809 -0
- package/test/provider/transform.test.ts +411 -0
- package/test/session/retry.test.ts +111 -0
- package/test/session/session.test.ts +71 -0
- package/test/skill/skill.test.ts +131 -0
- package/test/snapshot/snapshot.test.ts +939 -0
- package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
- package/test/tool/bash.test.ts +434 -0
- package/test/tool/grep.test.ts +108 -0
- package/test/tool/patch.test.ts +259 -0
- package/test/tool/read.test.ts +42 -0
- package/test/util/iife.test.ts +36 -0
- package/test/util/lazy.test.ts +50 -0
- package/test/util/timeout.test.ts +21 -0
- package/test/util/wildcard.test.ts +55 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sensitive Data Filter for RIRD Browser Agent
|
|
3
|
+
*
|
|
4
|
+
* Prevents sensitive user data from being sent to LLM APIs by detecting
|
|
5
|
+
* and redacting patterns like credit cards, SSNs, passwords, API keys, etc.
|
|
6
|
+
*
|
|
7
|
+
* The filter maintains a local mapping so responses can be de-redacted
|
|
8
|
+
* for the user if needed.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Log } from "@/util/log"
|
|
12
|
+
|
|
13
|
+
export namespace SensitiveFilter {
|
|
14
|
+
const log = Log.create({ service: "sensitive-filter" })
|
|
15
|
+
|
|
16
|
+
// Types for redaction
|
|
17
|
+
export type RedactionType =
|
|
18
|
+
| "CREDIT_CARD"
|
|
19
|
+
| "SSN"
|
|
20
|
+
| "PASSWORD"
|
|
21
|
+
| "API_KEY"
|
|
22
|
+
| "BEARER_TOKEN"
|
|
23
|
+
| "EMAIL"
|
|
24
|
+
|
|
25
|
+
export interface RedactionEntry {
|
|
26
|
+
type: RedactionType
|
|
27
|
+
original: string
|
|
28
|
+
placeholder: string
|
|
29
|
+
timestamp: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FilterResult {
|
|
33
|
+
text: string
|
|
34
|
+
redactions: RedactionEntry[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Session-scoped redaction mappings (in-memory for security - not persisted)
|
|
38
|
+
const redactionMappings = new Map<string, Map<string, RedactionEntry>>()
|
|
39
|
+
|
|
40
|
+
// Counter for unique placeholders per session
|
|
41
|
+
const placeholderCounters = new Map<string, Map<RedactionType, number>>()
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Luhn algorithm to validate credit card numbers
|
|
45
|
+
*/
|
|
46
|
+
function isValidLuhn(num: string): boolean {
|
|
47
|
+
const digits = num.replace(/\D/g, "")
|
|
48
|
+
if (digits.length < 13 || digits.length > 19) return false
|
|
49
|
+
|
|
50
|
+
let sum = 0
|
|
51
|
+
let isEven = false
|
|
52
|
+
|
|
53
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
54
|
+
let digit = parseInt(digits[i], 10)
|
|
55
|
+
|
|
56
|
+
if (isEven) {
|
|
57
|
+
digit *= 2
|
|
58
|
+
if (digit > 9) {
|
|
59
|
+
digit -= 9
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
sum += digit
|
|
64
|
+
isEven = !isEven
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return sum % 10 === 0
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get or create a unique placeholder for a redaction
|
|
72
|
+
*/
|
|
73
|
+
function getPlaceholder(sessionID: string, type: RedactionType): string {
|
|
74
|
+
if (!placeholderCounters.has(sessionID)) {
|
|
75
|
+
placeholderCounters.set(sessionID, new Map())
|
|
76
|
+
}
|
|
77
|
+
const counters = placeholderCounters.get(sessionID)!
|
|
78
|
+
|
|
79
|
+
const count = (counters.get(type) || 0) + 1
|
|
80
|
+
counters.set(type, count)
|
|
81
|
+
|
|
82
|
+
return `[REDACTED-${type}${count > 1 ? `-${count}` : ""}]`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Detect and redact credit card numbers (13-19 digits, Luhn valid)
|
|
87
|
+
* Handles formats: 1234567890123456, 1234-5678-9012-3456, 1234 5678 9012 3456
|
|
88
|
+
*/
|
|
89
|
+
function redactCreditCards(
|
|
90
|
+
text: string,
|
|
91
|
+
sessionID: string,
|
|
92
|
+
redactions: RedactionEntry[]
|
|
93
|
+
): string {
|
|
94
|
+
// Match sequences that look like credit card numbers
|
|
95
|
+
// Supports: plain digits, dash-separated, space-separated
|
|
96
|
+
const ccPattern = /\b(?:\d{4}[-\s]?){3}\d{4}\b|\b\d{13,19}\b/g
|
|
97
|
+
|
|
98
|
+
return text.replace(ccPattern, (match) => {
|
|
99
|
+
const digitsOnly = match.replace(/\D/g, "")
|
|
100
|
+
|
|
101
|
+
// Must be 13-19 digits and pass Luhn check
|
|
102
|
+
if (digitsOnly.length >= 13 && digitsOnly.length <= 19 && isValidLuhn(digitsOnly)) {
|
|
103
|
+
const placeholder = getPlaceholder(sessionID, "CREDIT_CARD")
|
|
104
|
+
const entry: RedactionEntry = {
|
|
105
|
+
type: "CREDIT_CARD",
|
|
106
|
+
original: match,
|
|
107
|
+
placeholder,
|
|
108
|
+
timestamp: Date.now(),
|
|
109
|
+
}
|
|
110
|
+
redactions.push(entry)
|
|
111
|
+
storeRedaction(sessionID, entry)
|
|
112
|
+
log.info("redacted credit card", { sessionID, placeholder })
|
|
113
|
+
return placeholder
|
|
114
|
+
}
|
|
115
|
+
return match
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Detect and redact SSN (XXX-XX-XXXX pattern)
|
|
121
|
+
*/
|
|
122
|
+
function redactSSN(
|
|
123
|
+
text: string,
|
|
124
|
+
sessionID: string,
|
|
125
|
+
redactions: RedactionEntry[]
|
|
126
|
+
): string {
|
|
127
|
+
// SSN pattern: 3 digits, dash, 2 digits, dash, 4 digits
|
|
128
|
+
// Also match without dashes but with proper grouping context
|
|
129
|
+
const ssnPattern = /\b\d{3}-\d{2}-\d{4}\b/g
|
|
130
|
+
|
|
131
|
+
return text.replace(ssnPattern, (match) => {
|
|
132
|
+
const placeholder = getPlaceholder(sessionID, "SSN")
|
|
133
|
+
const entry: RedactionEntry = {
|
|
134
|
+
type: "SSN",
|
|
135
|
+
original: match,
|
|
136
|
+
placeholder,
|
|
137
|
+
timestamp: Date.now(),
|
|
138
|
+
}
|
|
139
|
+
redactions.push(entry)
|
|
140
|
+
storeRedaction(sessionID, entry)
|
|
141
|
+
log.info("redacted SSN", { sessionID, placeholder })
|
|
142
|
+
return placeholder
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Detect and redact passwords in common patterns
|
|
148
|
+
* Matches: password: X, pwd: X, pass: X, passwd: X (case insensitive)
|
|
149
|
+
*/
|
|
150
|
+
function redactPasswords(
|
|
151
|
+
text: string,
|
|
152
|
+
sessionID: string,
|
|
153
|
+
redactions: RedactionEntry[]
|
|
154
|
+
): string {
|
|
155
|
+
// Match password patterns with various delimiters and quote styles
|
|
156
|
+
// password: value, password=value, password="value", etc.
|
|
157
|
+
const pwdPattern =
|
|
158
|
+
/\b(password|passwd|pwd|pass|secret)[\s]*[:=][\s]*(['"]?)([^\s'"}\]>,]+)\2/gi
|
|
159
|
+
|
|
160
|
+
return text.replace(pwdPattern, (match, label, quote, value) => {
|
|
161
|
+
// Don't redact placeholder-like values or very short values
|
|
162
|
+
if (value.length < 4 || value.startsWith("[REDACTED")) {
|
|
163
|
+
return match
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const placeholder = getPlaceholder(sessionID, "PASSWORD")
|
|
167
|
+
const entry: RedactionEntry = {
|
|
168
|
+
type: "PASSWORD",
|
|
169
|
+
original: value,
|
|
170
|
+
placeholder,
|
|
171
|
+
timestamp: Date.now(),
|
|
172
|
+
}
|
|
173
|
+
redactions.push(entry)
|
|
174
|
+
storeRedaction(sessionID, entry)
|
|
175
|
+
log.info("redacted password", { sessionID, placeholder })
|
|
176
|
+
|
|
177
|
+
// Preserve the label and delimiter, only replace the value
|
|
178
|
+
return `${label}${match.includes("=") ? "=" : ":"}${quote}${placeholder}${quote}`
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Detect and redact API keys in common patterns
|
|
184
|
+
* Matches: sk-*, api_key=*, apikey=*, api-key:*, etc.
|
|
185
|
+
*/
|
|
186
|
+
function redactAPIKeys(
|
|
187
|
+
text: string,
|
|
188
|
+
sessionID: string,
|
|
189
|
+
redactions: RedactionEntry[]
|
|
190
|
+
): string {
|
|
191
|
+
// Common API key patterns
|
|
192
|
+
const patterns = [
|
|
193
|
+
// sk-* (OpenAI style)
|
|
194
|
+
/\bsk-[a-zA-Z0-9_-]{20,}\b/g,
|
|
195
|
+
// xoxb-*, xoxp-* (Slack style)
|
|
196
|
+
/\bxox[bp]-[a-zA-Z0-9-]{20,}\b/g,
|
|
197
|
+
// ghp_*, gho_*, ghu_*, ghs_*, ghr_* (GitHub tokens)
|
|
198
|
+
/\bgh[pousr]_[a-zA-Z0-9]{36,}\b/g,
|
|
199
|
+
// AKIA* (AWS access keys)
|
|
200
|
+
/\bAKIA[A-Z0-9]{16}\b/g,
|
|
201
|
+
// Generic api_key=, apikey=, api-key: patterns
|
|
202
|
+
/\b(api[_-]?key|apikey|access[_-]?key|secret[_-]?key)[\s]*[:=][\s]*(['"]?)([a-zA-Z0-9_-]{16,})\2/gi,
|
|
203
|
+
// Generic *_API_KEY or *_SECRET patterns in env var style
|
|
204
|
+
/\b[A-Z_]+_(API_KEY|SECRET|TOKEN)[\s]*[:=][\s]*(['"]?)([a-zA-Z0-9_-]{16,})\2/g,
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
let result = text
|
|
208
|
+
|
|
209
|
+
for (const pattern of patterns) {
|
|
210
|
+
result = result.replace(pattern, (match, ...args) => {
|
|
211
|
+
// Don't redact already redacted values
|
|
212
|
+
if (match.includes("[REDACTED")) {
|
|
213
|
+
return match
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// For patterns with capture groups (api_key=value), only redact the value
|
|
217
|
+
if (typeof args[0] === "string" && args[2] && typeof args[2] === "string") {
|
|
218
|
+
const label = args[0]
|
|
219
|
+
const quote = args[1] || ""
|
|
220
|
+
const value = args[2]
|
|
221
|
+
|
|
222
|
+
const placeholder = getPlaceholder(sessionID, "API_KEY")
|
|
223
|
+
const entry: RedactionEntry = {
|
|
224
|
+
type: "API_KEY",
|
|
225
|
+
original: value,
|
|
226
|
+
placeholder,
|
|
227
|
+
timestamp: Date.now(),
|
|
228
|
+
}
|
|
229
|
+
redactions.push(entry)
|
|
230
|
+
storeRedaction(sessionID, entry)
|
|
231
|
+
log.info("redacted API key", { sessionID, placeholder })
|
|
232
|
+
|
|
233
|
+
return `${label}${match.includes("=") ? "=" : ":"}${quote}${placeholder}${quote}`
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// For standalone patterns (sk-*, ghp_*, etc.)
|
|
237
|
+
const placeholder = getPlaceholder(sessionID, "API_KEY")
|
|
238
|
+
const entry: RedactionEntry = {
|
|
239
|
+
type: "API_KEY",
|
|
240
|
+
original: match,
|
|
241
|
+
placeholder,
|
|
242
|
+
timestamp: Date.now(),
|
|
243
|
+
}
|
|
244
|
+
redactions.push(entry)
|
|
245
|
+
storeRedaction(sessionID, entry)
|
|
246
|
+
log.info("redacted API key", { sessionID, placeholder })
|
|
247
|
+
|
|
248
|
+
return placeholder
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return result
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Detect and redact Bearer tokens
|
|
257
|
+
* Matches: Bearer <token>, Authorization: Bearer <token>
|
|
258
|
+
*/
|
|
259
|
+
function redactBearerTokens(
|
|
260
|
+
text: string,
|
|
261
|
+
sessionID: string,
|
|
262
|
+
redactions: RedactionEntry[]
|
|
263
|
+
): string {
|
|
264
|
+
// Bearer token pattern
|
|
265
|
+
const bearerPattern = /\bBearer\s+([a-zA-Z0-9_.-]{20,})\b/gi
|
|
266
|
+
|
|
267
|
+
return text.replace(bearerPattern, (match, token) => {
|
|
268
|
+
if (token.startsWith("[REDACTED")) {
|
|
269
|
+
return match
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const placeholder = getPlaceholder(sessionID, "BEARER_TOKEN")
|
|
273
|
+
const entry: RedactionEntry = {
|
|
274
|
+
type: "BEARER_TOKEN",
|
|
275
|
+
original: token,
|
|
276
|
+
placeholder,
|
|
277
|
+
timestamp: Date.now(),
|
|
278
|
+
}
|
|
279
|
+
redactions.push(entry)
|
|
280
|
+
storeRedaction(sessionID, entry)
|
|
281
|
+
log.info("redacted Bearer token", { sessionID, placeholder })
|
|
282
|
+
|
|
283
|
+
return `Bearer ${placeholder}`
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Optional: Detect and redact email addresses
|
|
289
|
+
* Disabled by default as emails are often needed for tasks
|
|
290
|
+
*/
|
|
291
|
+
function redactEmails(
|
|
292
|
+
text: string,
|
|
293
|
+
sessionID: string,
|
|
294
|
+
redactions: RedactionEntry[],
|
|
295
|
+
enabled: boolean = false
|
|
296
|
+
): string {
|
|
297
|
+
if (!enabled) return text
|
|
298
|
+
|
|
299
|
+
// Basic email pattern
|
|
300
|
+
const emailPattern = /\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b/g
|
|
301
|
+
|
|
302
|
+
return text.replace(emailPattern, (match) => {
|
|
303
|
+
const placeholder = getPlaceholder(sessionID, "EMAIL")
|
|
304
|
+
const entry: RedactionEntry = {
|
|
305
|
+
type: "EMAIL",
|
|
306
|
+
original: match,
|
|
307
|
+
placeholder,
|
|
308
|
+
timestamp: Date.now(),
|
|
309
|
+
}
|
|
310
|
+
redactions.push(entry)
|
|
311
|
+
storeRedaction(sessionID, entry)
|
|
312
|
+
log.info("redacted email", { sessionID, placeholder })
|
|
313
|
+
return placeholder
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Store a redaction entry in the session mapping
|
|
319
|
+
*/
|
|
320
|
+
function storeRedaction(sessionID: string, entry: RedactionEntry): void {
|
|
321
|
+
if (!redactionMappings.has(sessionID)) {
|
|
322
|
+
redactionMappings.set(sessionID, new Map())
|
|
323
|
+
}
|
|
324
|
+
redactionMappings.get(sessionID)!.set(entry.placeholder, entry)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Filter options
|
|
329
|
+
*/
|
|
330
|
+
export interface FilterOptions {
|
|
331
|
+
/** Whether to redact email addresses (default: false) */
|
|
332
|
+
redactEmails?: boolean
|
|
333
|
+
/** Session ID for tracking redactions */
|
|
334
|
+
sessionID: string
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Filter sensitive data from text before sending to LLM
|
|
339
|
+
*
|
|
340
|
+
* @param text The text to filter
|
|
341
|
+
* @param options Filter options including sessionID
|
|
342
|
+
* @returns FilterResult with filtered text and list of redactions made
|
|
343
|
+
*/
|
|
344
|
+
export function filter(text: string, options: FilterOptions): FilterResult {
|
|
345
|
+
const redactions: RedactionEntry[] = []
|
|
346
|
+
let filtered = text
|
|
347
|
+
|
|
348
|
+
// Apply all redaction functions in order
|
|
349
|
+
// Order matters - more specific patterns should run first
|
|
350
|
+
filtered = redactCreditCards(filtered, options.sessionID, redactions)
|
|
351
|
+
filtered = redactSSN(filtered, options.sessionID, redactions)
|
|
352
|
+
filtered = redactBearerTokens(filtered, options.sessionID, redactions)
|
|
353
|
+
filtered = redactAPIKeys(filtered, options.sessionID, redactions)
|
|
354
|
+
filtered = redactPasswords(filtered, options.sessionID, redactions)
|
|
355
|
+
filtered = redactEmails(filtered, options.sessionID, redactions, options.redactEmails)
|
|
356
|
+
|
|
357
|
+
if (redactions.length > 0) {
|
|
358
|
+
log.info("filtered sensitive data", {
|
|
359
|
+
sessionID: options.sessionID,
|
|
360
|
+
redactionCount: redactions.length,
|
|
361
|
+
types: Array.from(new Set(redactions.map((r) => r.type))),
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
text: filtered,
|
|
367
|
+
redactions,
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Restore redacted values in text (for displaying to user)
|
|
373
|
+
*
|
|
374
|
+
* @param text Text containing redaction placeholders
|
|
375
|
+
* @param sessionID Session ID to look up redactions
|
|
376
|
+
* @returns Text with placeholders replaced by original values
|
|
377
|
+
*/
|
|
378
|
+
export function restore(text: string, sessionID: string): string {
|
|
379
|
+
const mapping = redactionMappings.get(sessionID)
|
|
380
|
+
if (!mapping) return text
|
|
381
|
+
|
|
382
|
+
let restored = text
|
|
383
|
+
for (const [placeholder, entry] of Array.from(mapping.entries())) {
|
|
384
|
+
restored = restored.replace(new RegExp(escapeRegex(placeholder), "g"), entry.original)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return restored
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get all redactions for a session
|
|
392
|
+
*/
|
|
393
|
+
export function getRedactions(sessionID: string): RedactionEntry[] {
|
|
394
|
+
const mapping = redactionMappings.get(sessionID)
|
|
395
|
+
if (!mapping) return []
|
|
396
|
+
return Array.from(mapping.values())
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Clear redaction mappings for a session (call when session ends)
|
|
401
|
+
*/
|
|
402
|
+
export function clearSession(sessionID: string): void {
|
|
403
|
+
redactionMappings.delete(sessionID)
|
|
404
|
+
placeholderCounters.delete(sessionID)
|
|
405
|
+
log.info("cleared sensitive data mappings", { sessionID })
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Check if text contains any sensitive patterns (without redacting)
|
|
410
|
+
* Useful for warnings or UI indicators
|
|
411
|
+
*/
|
|
412
|
+
export function containsSensitive(text: string): { hasSensitive: boolean; types: RedactionType[] } {
|
|
413
|
+
const types: RedactionType[] = []
|
|
414
|
+
|
|
415
|
+
// Check for credit card patterns
|
|
416
|
+
const ccPattern = /\b(?:\d{4}[-\s]?){3}\d{4}\b|\b\d{13,19}\b/g
|
|
417
|
+
const ccMatches = text.match(ccPattern)
|
|
418
|
+
if (ccMatches) {
|
|
419
|
+
for (const match of ccMatches) {
|
|
420
|
+
const digits = match.replace(/\D/g, "")
|
|
421
|
+
if (digits.length >= 13 && digits.length <= 19 && isValidLuhn(digits)) {
|
|
422
|
+
types.push("CREDIT_CARD")
|
|
423
|
+
break
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Check for SSN pattern
|
|
429
|
+
if (/\b\d{3}-\d{2}-\d{4}\b/.test(text)) {
|
|
430
|
+
types.push("SSN")
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Check for password patterns
|
|
434
|
+
if (/\b(password|passwd|pwd|pass|secret)[\s]*[:=]/i.test(text)) {
|
|
435
|
+
types.push("PASSWORD")
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Check for API key patterns
|
|
439
|
+
if (
|
|
440
|
+
/\bsk-[a-zA-Z0-9_-]{20,}\b/.test(text) ||
|
|
441
|
+
/\bxox[bp]-[a-zA-Z0-9-]{20,}\b/.test(text) ||
|
|
442
|
+
/\bgh[pousr]_[a-zA-Z0-9]{36,}\b/.test(text) ||
|
|
443
|
+
/\bAKIA[A-Z0-9]{16}\b/.test(text) ||
|
|
444
|
+
/\b(api[_-]?key|apikey|access[_-]?key|secret[_-]?key)[\s]*[:=]/i.test(text)
|
|
445
|
+
) {
|
|
446
|
+
types.push("API_KEY")
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Check for Bearer tokens
|
|
450
|
+
if (/\bBearer\s+[a-zA-Z0-9_.-]{20,}\b/i.test(text)) {
|
|
451
|
+
types.push("BEARER_TOKEN")
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
hasSensitive: types.length > 0,
|
|
456
|
+
types: Array.from(new Set(types)),
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Helper to escape special regex characters
|
|
462
|
+
*/
|
|
463
|
+
function escapeRegex(str: string): string {
|
|
464
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
465
|
+
}
|
|
466
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { BusEvent } from "@/bus/bus-event"
|
|
2
|
+
import { Bus } from "@/bus"
|
|
3
|
+
import { Instance } from "@/project/instance"
|
|
4
|
+
import z from "zod"
|
|
5
|
+
|
|
6
|
+
export namespace SessionStatus {
|
|
7
|
+
export const Info = z
|
|
8
|
+
.union([
|
|
9
|
+
z.object({
|
|
10
|
+
type: z.literal("idle"),
|
|
11
|
+
}),
|
|
12
|
+
z.object({
|
|
13
|
+
type: z.literal("retry"),
|
|
14
|
+
attempt: z.number(),
|
|
15
|
+
message: z.string(),
|
|
16
|
+
next: z.number(),
|
|
17
|
+
}),
|
|
18
|
+
z.object({
|
|
19
|
+
type: z.literal("busy"),
|
|
20
|
+
}),
|
|
21
|
+
])
|
|
22
|
+
.meta({
|
|
23
|
+
ref: "SessionStatus",
|
|
24
|
+
})
|
|
25
|
+
export type Info = z.infer<typeof Info>
|
|
26
|
+
|
|
27
|
+
export const Event = {
|
|
28
|
+
Status: BusEvent.define(
|
|
29
|
+
"session.status",
|
|
30
|
+
z.object({
|
|
31
|
+
sessionID: z.string(),
|
|
32
|
+
status: Info,
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
35
|
+
// deprecated
|
|
36
|
+
Idle: BusEvent.define(
|
|
37
|
+
"session.idle",
|
|
38
|
+
z.object({
|
|
39
|
+
sessionID: z.string(),
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const state = Instance.state(() => {
|
|
45
|
+
const data: Record<string, Info> = {}
|
|
46
|
+
return data
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export function get(sessionID: string) {
|
|
50
|
+
return (
|
|
51
|
+
state()[sessionID] ?? {
|
|
52
|
+
type: "idle",
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function list() {
|
|
58
|
+
return state()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function set(sessionID: string, status: Info) {
|
|
62
|
+
Bus.publish(Event.Status, {
|
|
63
|
+
sessionID,
|
|
64
|
+
status,
|
|
65
|
+
})
|
|
66
|
+
if (status.type === "idle") {
|
|
67
|
+
// deprecated
|
|
68
|
+
Bus.publish(Event.Idle, {
|
|
69
|
+
sessionID,
|
|
70
|
+
})
|
|
71
|
+
delete state()[sessionID]
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
state()[sessionID] = status
|
|
75
|
+
}
|
|
76
|
+
}
|