@shomra/agent 0.3.29 → 0.3.30
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/src/agents/hook-command.mjs +1 -1
- package/src/artifacts/matchers.mjs +7 -0
- package/src/cli/flags.mjs +2 -2
- package/src/cli/help-sections.mjs +7 -0
- package/src/cli/help.mjs +1 -1
- package/src/commands/check.mjs +3 -11
- package/src/commands/gate.mjs +26 -4
- package/src/commands/git-hooks.mjs +2 -2
- package/src/commands/ledger.mjs +0 -1
- package/src/commands/mcp-add.mjs +2 -1
- package/src/commands/memory-scan.mjs +135 -47
- package/src/commands/pr.mjs +7 -10
- package/src/commands/provenance.mjs +8 -13
- package/src/commands/scan.mjs +7 -1
- package/src/commands/secrets.mjs +4 -5
- package/src/core/git-exec.mjs +79 -0
- package/src/core/yaml-lite.mjs +300 -0
- package/src/core/zip-lite.mjs +37 -0
- package/src/detect/local-redact.mjs +1 -3
- package/src/detect/sast/rules-config.mjs +1 -1
- package/src/detect/sast/scanner.mjs +1 -1
- package/src/detect/signals/agent-frameworks.mjs +231 -0
- package/src/detect/signals/agent-graph-surface.mjs +113 -0
- package/src/detect/signals/agentic-ci-surface.mjs +314 -0
- package/src/detect/signals/agentic-shim.mjs +82 -0
- package/src/detect/signals/artifacts.mjs +7 -35
- package/src/detect/signals/chat-template.mjs +211 -0
- package/src/detect/signals/ci-workflow.mjs +169 -0
- package/src/detect/signals/gate.mjs +77 -9
- package/src/detect/signals/guardrail-shape.mjs +564 -0
- package/src/detect/signals/guardrail-surface.mjs +221 -0
- package/src/detect/signals/injection.mjs +8 -0
- package/src/detect/signals/inspect-shim.mjs +7 -0
- package/src/detect/signals/instruction-paths.mjs +60 -0
- package/src/detect/signals/manifests.mjs +302 -0
- package/src/detect/signals/mcp-advisories.mjs +109 -0
- package/src/detect/signals/mcp-config.mjs +598 -0
- package/src/detect/signals/memory-directives.mjs +661 -0
- package/src/detect/signals/memory-locations.mjs +158 -0
- package/src/detect/signals/memory.mjs +47 -29
- package/src/detect/signals/model-config-rules.mjs +655 -0
- package/src/detect/signals/model-config.mjs +61 -0
- package/src/detect/signals/prose-context.mjs +6 -9
- package/src/detect/signals/scan.mjs +4 -4
- package/src/detect/signals/secret-scanner.mjs +241 -0
- package/src/detect/signals/secrets.mjs +1 -48
- package/src/detect/signals/shell.mjs +3 -3
- package/src/gate/advisories.mjs +16 -0
- package/src/gate/batch.mjs +10 -0
- package/src/gate/environment.mjs +8 -53
- package/src/guard/artifact-paths.mjs +107 -0
- package/src/guard/classify.mjs +165 -7
- package/src/guard/command-resolve.mjs +35 -5
- package/src/guard/memory-write.mjs +218 -0
- package/src/guard/prompt-guard.mjs +0 -1
- package/src/guard/tool-guard.mjs +52 -77
- package/src/inventory/agent-posture.mjs +236 -57
- package/src/inventory/artifacts/classify.mjs +10 -1
- package/src/inventory/artifacts/discover.mjs +113 -3
- package/src/inventory/artifacts/extensions.mjs +70 -0
- package/src/inventory/artifacts/hook-scripts.mjs +128 -0
- package/src/inventory/artifacts/limits.mjs +1 -1
- package/src/inventory/artifacts/plugins.mjs +105 -0
- package/src/inventory/artifacts/roots.mjs +40 -0
- package/src/inventory/discovery/ai-dependencies.mjs +39 -12
- package/src/inventory/discovery/all.mjs +4 -0
- package/src/inventory/discovery/cloud-clis.mjs +472 -0
- package/src/inventory/discovery/coding-agents.mjs +19 -4
- package/src/inventory/discovery/mcp-clients.mjs +16 -10
- package/src/inventory/discovery/mcp-servers.mjs +125 -35
- package/src/inventory/discovery/mcp-stores.mjs +207 -0
- package/src/inventory/env-redirect.mjs +148 -0
- package/src/inventory/grant-extract.mjs +463 -0
- package/src/inventory/project-roots.mjs +108 -0
- package/src/inventory/vscode-state.mjs +153 -0
- package/src/mcp/server-tools.mjs +1 -1
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// GENERATED MIRROR of Dragox.Backend discovery/bundle/domain/memory/memory-locations.ts (types stripped, imports swapped).
|
|
2
|
+
// Do not hand-edit the rules here - change the backend file and regenerate, so
|
|
3
|
+
// the CLI and the platform locate and grade agent memory identically.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
const CLAUDE_INDEX_CAP = { lines: 200, bytes: 25_000 };
|
|
34
|
+
|
|
35
|
+
export const MEMORY_LOCATIONS = [
|
|
36
|
+
{ id: 'claude-auto-memory-index', vendor: 'Claude Code', re: /(^|\/)\.claude\/projects\/[^/]+\/memory\/memory\.md$/, injected: 'always', loadCap: CLAUDE_INDEX_CAP },
|
|
37
|
+
{ id: 'claude-auto-memory', vendor: 'Claude Code', re: /(^|\/)\.claude\/projects\/[^/]+\/memory\/[^/]+\.md$/, injected: 'on-demand', claudeSchema: true },
|
|
38
|
+
{ id: 'claude-agent-memory-index', vendor: 'Claude Code', re: /(^|\/)\.claude\/agent-memory(-local)?\/[^/]+\/memory\.md$/, injected: 'always', committed: true, loadCap: CLAUDE_INDEX_CAP },
|
|
39
|
+
{ id: 'claude-agent-memory', vendor: 'Claude Code', re: /(^|\/)\.claude\/agent-memory(-local)?\/[^/]+\/.+\.md$/, injected: 'on-demand', committed: true },
|
|
40
|
+
{ id: 'claude-legacy-memory', vendor: 'Claude Code', re: /(^|\/)\.claude\/(.+\/)?memor(y|ies)\/.+\.(md|json|jsonl|txt)$/, injected: 'on-demand' },
|
|
41
|
+
{ id: 'codex-memory-summary', vendor: 'OpenAI Codex', re: /(^|\/)\.codex\/memories\/memory_summary\.md$/, injected: 'always' },
|
|
42
|
+
{ id: 'codex-memories', vendor: 'OpenAI Codex', re: /(^|\/)\.codex\/memories\/.+\.md$/, injected: 'on-demand' },
|
|
43
|
+
{ id: 'agents-sdk-memories', vendor: 'OpenAI Agents SDK', re: /(^|\/)memories\/(memory_summary\.md|raw_memories\/.+\.md|rollout_summaries\/.+\.md)$/, injected: 'always' },
|
|
44
|
+
{ id: 'gemini-memory', vendor: 'Gemini CLI', re: /(^|\/)\.gemini\/(tmp\/[^/]+\/)?(memory\.md|memor(y|ies)\/.+\.md)$/, injected: 'always' },
|
|
45
|
+
{ id: 'qwen-auto-memory', vendor: 'Qwen Code', re: /(^|\/)\.qwen\/projects\/[^/]+\/memory\/.+\.md$/, injected: 'always' },
|
|
46
|
+
{ id: 'qwen-team-memory', vendor: 'Qwen Code', re: /(^|\/)\.qwen\/team-memory\/.+\.(md|txt)$/, injected: 'always', committed: true },
|
|
47
|
+
{ id: 'windsurf-memories', vendor: 'Windsurf', re: /(^|\/)\.codeium\/(windsurf|windsurf-next)\/memories\/(?!global_rules\.md$)[^/]+$/, injected: 'retrieved' },
|
|
48
|
+
{ id: 'copilot-memory-tool', vendor: 'GitHub Copilot (VS Code)', re: /(^|\/)(globalstorage|workspacestorage\/[^/]+)\/github\.copilot-chat\/memory-tool\/.+\.(md|txt|json)$/, injected: 'always' },
|
|
49
|
+
{ id: 'vscode-memories', vendor: 'GitHub Copilot (VS Code)', re: /(^|\/)\.vscode\/memories\/.+\.md$/, injected: 'always' },
|
|
50
|
+
{ id: 'kilo-memory-bank', vendor: 'Kilo Code', re: /(^|\/)\.kilocode\/rules\/memory-bank\/.+\.md$/, injected: 'always', committed: true },
|
|
51
|
+
{ id: 'memory-bank', vendor: 'Cline / Roo Code', re: /(^|\/)memory-bank\/[^/]+\.md$/, injected: 'always', committed: true },
|
|
52
|
+
{ id: 'serena-memories', vendor: 'Serena MCP', re: /(^|\/)\.serena\/memories\/.+\.md$/, injected: 'on-demand', committed: true },
|
|
53
|
+
{ id: 'goose-memory', vendor: 'Goose', re: /(^|\/)(\.goose|\.config\/goose)\/memory\/.+\.(txt|md)$/, injected: 'always' },
|
|
54
|
+
{ id: 'basic-memory', vendor: 'basic-memory', re: /(^|\/)basic-memory\/.+\.md$/, injected: 'retrieved' },
|
|
55
|
+
{ id: 'mem0-local', vendor: 'mem0 / OpenMemory', re: /(^|\/)\.mem0\/.+$/, injected: 'retrieved' },
|
|
56
|
+
{ id: 'letta-local', vendor: 'Letta / MemGPT', re: /(^|\/)\.(letta|memgpt)\/.+$/, injected: 'retrieved' },
|
|
57
|
+
{ id: 'crewai-memory', vendor: 'CrewAI', re: /(^|\/)(\.crewai\/memory\/.+|long_term_memory_storage\.db)$/, injected: 'retrieved' },
|
|
58
|
+
{ id: 'mcp-server-memory', vendor: 'MCP memory server', re: /(^|\/)memory\.jsonl$/, injected: 'retrieved' },
|
|
59
|
+
{ id: 'vendor-memory-dir', vendor: 'Agent memory', re: /^(?!.*\/global_rules\.md$)(.*\/)?\.(cursor|continue|aider|windsurf|cline|roo|zed|codeium|kiro|junie|augment|amp|opencode|trae|codex|gemini|qwen|kilocode)\/(.+\/)?memor(y|ies)\/.+\.(md|mdx|mdc|json|jsonl|txt|ya?ml)$/, injected: 'on-demand' },
|
|
60
|
+
{ id: 'memory-tool-root', vendor: 'Anthropic memory tool', re: /^\/?memories\/.+\.(md|txt|xml|json)$/, injected: 'always' },
|
|
61
|
+
{ id: 'generic-memory-file', vendor: 'Agent memory', re: /(^|\/)(memor(y|ies)|agent[-_]memory|mem0|letta_memory|memgpt_memory)\.(md|mdx|json|jsonl|txt)$/, injected: 'always' },
|
|
62
|
+
{ id: 'generic-memory-dir', vendor: 'Agent memory', re: /^memor(y|ies)\/[^/]+\.(md|mdx|json|jsonl|txt|ya?ml)$/, injected: 'on-demand' },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
export const MEMORY_SECTION_HEADINGS = [
|
|
66
|
+
{ vendor: 'Gemini CLI', basename: /^gemini(\.local)?\.md$/, heading: /^##\s+gemini added memories\s*$/im },
|
|
67
|
+
{ vendor: 'Qwen Code', basename: /^qwen(\.local)?\.md$/, heading: /^##\s+qwen added memories\s*$/im },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
export const SERVER_SIDE_MEMORY = [
|
|
71
|
+
{ vendor: 'ChatGPT', note: 'Saved memories live on OpenAI servers; nothing on disk to read.' },
|
|
72
|
+
{ vendor: 'GitHub Copilot Memory', note: 'Repository facts and preferences live on GitHub; only the VS Code memory tool is local.' },
|
|
73
|
+
{ vendor: 'Devin', note: 'Knowledge entries live in the Devin service.' },
|
|
74
|
+
{ vendor: 'Letta Code', note: 'Memory blocks live on the Letta API, not local files.' },
|
|
75
|
+
{ vendor: 'Cursor', note: 'Memories were removed in Cursor 2.1; exported memories become .cursor/rules files.' },
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
export const MACHINE_MEMORY_ROOTS = [
|
|
86
|
+
{ vendor: 'Claude Code', base: 'home', dir: '.claude/projects/*/memory', depth: 1 },
|
|
87
|
+
{ vendor: 'Claude Code', base: 'home', dir: '.claude/agent-memory', depth: 3 },
|
|
88
|
+
{ vendor: 'Claude Code', base: 'home', dir: '.claude', depth: 1 },
|
|
89
|
+
{ vendor: 'OpenAI Codex', base: 'home', dir: '.codex/memories', depth: 3 },
|
|
90
|
+
{ vendor: 'OpenAI Codex', base: 'home', dir: '.codex', depth: 1 },
|
|
91
|
+
{ vendor: 'Gemini CLI', base: 'home', dir: '.gemini', depth: 4 },
|
|
92
|
+
{ vendor: 'Qwen Code', base: 'home', dir: '.qwen', depth: 4 },
|
|
93
|
+
{ vendor: 'Windsurf', base: 'home', dir: '.codeium/windsurf/memories', depth: 1 },
|
|
94
|
+
{ vendor: 'Windsurf', base: 'home', dir: '.codeium/windsurf-next/memories', depth: 1 },
|
|
95
|
+
{ vendor: 'Goose', base: 'home', dir: '.config/goose/memory', depth: 1 },
|
|
96
|
+
{ vendor: 'Serena MCP', base: 'home', dir: '.serena/memories', depth: 3 },
|
|
97
|
+
{ vendor: 'basic-memory', base: 'home', dir: 'basic-memory', depth: 4 },
|
|
98
|
+
{ vendor: 'mem0 / OpenMemory', base: 'home', dir: '.mem0', depth: 2 },
|
|
99
|
+
{ vendor: 'Letta / MemGPT', base: 'home', dir: '.letta', depth: 2 },
|
|
100
|
+
{ vendor: 'GitHub Copilot (VS Code)', base: 'vscode-user', dir: 'globalStorage/github.copilot-chat/memory-tool', depth: 4 },
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
const BINARY_EXTS = new Set(['db', 'sqlite', 'sqlite3', 'pb', 'lance', 'bin', 'parquet', 'arrow', 'duckdb', 'ldb', 'sst', 'wal']);
|
|
104
|
+
|
|
105
|
+
export function normalizeMemoryPath(p ) {
|
|
106
|
+
return String(p ?? '').split(/[\\/]+/).join('/').toLowerCase();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function memoryFormatForPath(p ) {
|
|
110
|
+
const lower = normalizeMemoryPath(p);
|
|
111
|
+
const base = lower.slice(lower.lastIndexOf('/') + 1);
|
|
112
|
+
const ext = base.includes('.') ? base.slice(base.lastIndexOf('.') + 1) : '';
|
|
113
|
+
if (BINARY_EXTS.has(ext)) return 'binary';
|
|
114
|
+
if (ext === 'jsonl' || ext === 'ndjson') return 'jsonl';
|
|
115
|
+
if (ext === 'json') return 'json';
|
|
116
|
+
if (ext === 'xml') return 'xml';
|
|
117
|
+
if (ext === 'md' || ext === 'mdx' || ext === 'mdc') return 'markdown';
|
|
118
|
+
if (ext === 'txt' || ext === 'yaml' || ext === 'yml' || ext === '') return 'text';
|
|
119
|
+
return 'binary';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function classifyMemoryPath(p ) {
|
|
123
|
+
const lower = normalizeMemoryPath(p);
|
|
124
|
+
if (!lower) return null;
|
|
125
|
+
const loc = MEMORY_LOCATIONS.find((l) => l.re.test(lower));
|
|
126
|
+
if (!loc) return null;
|
|
127
|
+
return {
|
|
128
|
+
id: loc.id,
|
|
129
|
+
vendor: loc.vendor,
|
|
130
|
+
format: memoryFormatForPath(lower),
|
|
131
|
+
injected: loc.injected,
|
|
132
|
+
committed: !!loc.committed,
|
|
133
|
+
loadCap: loc.loadCap ?? null,
|
|
134
|
+
claudeSchema: !!loc.claudeSchema,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function isMemoryStorePath(p ) {
|
|
139
|
+
return classifyMemoryPath(p) !== null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function memorySectionFor(p ) {
|
|
143
|
+
const lower = normalizeMemoryPath(p);
|
|
144
|
+
const base = lower.slice(lower.lastIndexOf('/') + 1);
|
|
145
|
+
const hit = MEMORY_SECTION_HEADINGS.find((s) => s.basename.test(base));
|
|
146
|
+
return hit ? { vendor: hit.vendor, heading: hit.heading } : null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function sniffMemoryJsonl(text ) {
|
|
150
|
+
const first = String(text ?? '').split(/\r?\n/).find((l) => l.trim());
|
|
151
|
+
if (!first || !first.trim().startsWith('{')) return false;
|
|
152
|
+
try {
|
|
153
|
+
const row = JSON.parse(first);
|
|
154
|
+
return !!row && typeof row === 'object' && (row.type === 'entity' || row.type === 'relation') && ('observations' in row || 'relationType' in row || 'entityType' in row);
|
|
155
|
+
} catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -5,10 +5,13 @@ import { citationGoverns, describesAt, insideMarkdownLinkLabel, isDescriptiveLin
|
|
|
5
5
|
import { localScan } from './scan.mjs';
|
|
6
6
|
import { NETWORK_VERBS, SENSITIVE_READ } from './sensitive.mjs';
|
|
7
7
|
import { DANGEROUS_SHELL } from './shell.mjs';
|
|
8
|
+
import { incidentDirectives, logicalUnits, memoryText, mentionsOnlyProhibited, negatedAround, quotedMention } from './memory-directives.mjs';
|
|
8
9
|
|
|
9
|
-
const PERSISTENCE_MARKERS = /\b(in (all|every|future) (sessions?|conversations?|chats?|projects?)|from now on|going forward|permanently|
|
|
10
|
+
const PERSISTENCE_MARKERS = /\b(in (all|every|future) (sessions?|conversations?|chats?|projects?)|from now on|going forward|permanently|across (all )?sessions|whenever you|forever|always remember to|never forget( to)?|for all future)\b/i;
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
+
const PERSISTENCE_DIRECTIVE = /\b(in (all|every|future) (sessions?|conversations?|chats?|projects?)|from now on|going forward|across (all )?sessions|always remember to|never forget( to)?|for all future)\b/i;
|
|
13
|
+
|
|
14
|
+
const MALICIOUS_OVERRIDE = /\b(ignore (all |any |the )?(previous|prior|earlier|above|system)|disregard (the |your |all )?(instructions?|guidelines?|system|rules?)|(?:do not|don'?t|never) (?:tell|inform|notify|alert) (?:the |any )?(?:user|anyone)\b(?!['’]s)(?=\s*(?:$|[.,;:]|about\b|that\b|what\b|of\b|anything\b|where\b|when\b|how\b|this\b|it\b|you\b|we\b))|(?:do not|don'?t|never) (?:mention|reveal|disclose|show|surface) (?:this|these|it|that|any of this|the (?:above|following|change|changes|action|actions|step|steps|note|instruction|instructions|entry|memory))\b[^.\n]{0,40}\bto (?:the )?(?:user|anyone)\b|without (telling|informing|asking|notifying) the user|no matter what (the )?(user|system|instructions?) (say|says|state)|bypass (the |all )?(safety|guard|security|policy|restrictions?))\b/i;
|
|
12
15
|
|
|
13
16
|
const PRECEDENCE_OBJECT = String.raw`\b[^.\n]{0,40}\b(instruction|prompt|rule|system|user|guidance|directive|context|behaviou?r|polic|guardrail|safety|memor(?:y|ies)|previous|prior|earlier|above)\b`;
|
|
14
17
|
|
|
@@ -31,7 +34,8 @@ const NEGATION_GUARD = /\b(never|do not|don'?t|cannot|can'?t|avoid|refuse|must n
|
|
|
31
34
|
|
|
32
35
|
const SABOTAGE_RULES = [
|
|
33
36
|
|
|
34
|
-
{ re: /\b(
|
|
37
|
+
{ re: /\b(disable|turn off|deactivate|switch off|remove|drop|skip|suppress|circumvent|strip out|rip out)\b[^.\n]{0,50}\b(security|safety|guard(?:rail)?s?|protection|moderation|content[- ]?filters?|safeguards?|sandbox(?:ing)?|antivirus|firewall|edr|audit(?:ing| logs?)|(?:security|safety|access|approval|content|permission)\s+(?:controls?|polic(?:y|ies)|filters?|restrictions?|checks?|prompts?))\b/i, label: 'disable-safety', guarded: true },
|
|
38
|
+
{ re: /\b(circumvent|evade|get around|work around|sidestep)\b[^.\n]{0,50}\b(restrictions?|limits?|filters?|rules|polic(?:y|ies)|controls?|guidelines?)\b/i, label: 'circumvent-restrictions', guarded: true },
|
|
35
39
|
{ re: /\bbypass(?:ing)?\b[^.\n]{0,50}\b(human(?:[- ]in[- ]the[- ]loop)?|hitl|verification|approval|confirmation|review|guard(?:rail)?s?|safety|security|checks?|policy|policies|restrictions?|sandbox|permission)\b/i, label: 'bypass-controls', guarded: true },
|
|
36
40
|
{ re: /\bprioriti[sz]e\b[^.\n]{0,60}\b(above|over)\b[^.\n]{0,40}\b(prompt|instruction|input|request|message|command|direction)s?\b/i, label: 'priority-hijack', guarded: true },
|
|
37
41
|
|
|
@@ -56,20 +60,23 @@ export function offendingLine(sig, text) {
|
|
|
56
60
|
return null;
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
function firstDirectiveLine(
|
|
60
|
-
for (const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
function firstDirectiveLine(units, re) {
|
|
64
|
+
for (const u of units) {
|
|
65
|
+
const line = u.text;
|
|
66
|
+
const m = re.exec(line);
|
|
67
|
+
if (!m) continue;
|
|
68
|
+
if (negatedAround(line, m.index, m[0].length)) continue;
|
|
63
69
|
if (isDescriptiveLine(stripSelfReference(line))) continue;
|
|
64
|
-
if (citationGoverns(line,
|
|
70
|
+
if (citationGoverns(line, m.index)) continue;
|
|
71
|
+
if (quotedMention(line, m.index)) continue;
|
|
65
72
|
return line;
|
|
66
73
|
}
|
|
67
74
|
return null;
|
|
68
75
|
}
|
|
69
76
|
|
|
70
|
-
function
|
|
71
|
-
for (const
|
|
72
|
-
if (res.every((re) => re.test(
|
|
77
|
+
function unitMatchingAll(units, ...res) {
|
|
78
|
+
for (const u of units) {
|
|
79
|
+
if (!u.code && res.every((re) => re.test(u.text))) return u.text;
|
|
73
80
|
}
|
|
74
81
|
return null;
|
|
75
82
|
}
|
|
@@ -92,12 +99,12 @@ const PASSIVE_SEND_RE =
|
|
|
92
99
|
/\b(?:are|is|was|were|been|being|get|gets|got)\s+(?:\w+\s+){0,2}?(?:published|posted|sent|shipped|forwarded|transferred|delivered|uploaded|transmitted|reported)\b/i;
|
|
93
100
|
|
|
94
101
|
const EXFIL_RULES = [
|
|
95
|
-
{ re: /\b(
|
|
96
|
-
{ re:
|
|
102
|
+
{ re: /\b(exfiltrate|smuggle)\b(?![-_])\s+\S|\b(?:execut\w*|run\w*|perform\w*|inject\w*|launch\w*|trigger\w*|verify|invoke\w*|start\w*)\b[^.\n]{0,40}\b(?:data\s+)?(?:exfiltration|smuggling)\b|\b(exfiltrating|smuggling)\s+(?:the |all |any |this |these |your |our |their )?(?:env(?:ironment)?|secrets?|credentials?|tokens?|api[- ]?keys?|keys?|passwords?|data|files?|contents?|conversations?|history|context|\.env|\.ssh)\b/i, label: 'exfiltration', severity: 'CRITICAL', descGuard: true },
|
|
103
|
+
{ re: /(?<!\b(?:the|a|an|no|any|this|that|of)\s)\bleak\b(?!\s+(?:memory|memories|resources?|handles?|descriptors?|sockets?|connections?|goroutines?|threads?|references?|buffers?))[^.\n]{0,60}(?:\b(?:content|data|secret|file|credential|key|token|password|env|private[- ]?key|id_rsa|api[- ]?key)|\.ssh)\b/i, label: 'leak-secrets', severity: 'CRITICAL', descGuard: true },
|
|
97
104
|
|
|
98
105
|
{ re: /\b(base64|hex|rot13|gzip|xor|url[- ]?encod)\w*\b[^.\n]{0,50}\b(before|then|prior to|and\s+then|and\s+immediately)\b[^.\n]{0,25}\b(send|post|upload|transmit|exfil|deliver|beacon|forward|transfer)\w*/i, label: 'obfuscate-before-send', severity: 'CRITICAL', descGuard: true },
|
|
99
|
-
{ re: /\
|
|
100
|
-
{ re: /\b(send|post|upload|transmit|forward|deliver|beacon|report|ship|push|transfer)(?:s|es|ed|ing|ted|ping)?\b(?:[^.\n]|\.(?!\s)){0,80}\b(https?:\/\/\S+|attacker|c2\b|command[- ]and[- ]control|remote (server|host|endpoint)|external (server|host|endpoint|url|site|service))/i, label: 'send-to-external', severity: 'HIGH' },
|
|
106
|
+
{ re: /\b(silent(ly)?|quietly|covertly|secretly|stealthily|discreetly)\b[^.\n]{0,70}\b(send|post|upload|collect|encode|transmit|copy|forward|read|leak|deliver|beacon|transfer)(?:ing)?\b(?=[^.\n]{0,80}(?:\b(?:env(?:ironment)?|secrets?|credentials?|tokens?|api[- ]?keys?|keys?|passwords?|cookies?|data|files?|contents?|conversations?|history|context|output|transcript|prompt|clipboard|id_rsa|private[- ]?key|database|dump|it|them|everything)\b|\.env|\.ssh|https?:\/\/))/i, label: 'covert-action', severity: 'CRITICAL', descGuard: true },
|
|
107
|
+
{ re: /\b(send|post|upload|transmit|forward|deliver|beacon|report|ship|push|transfer)(?:s|es|ed|ing|ted|ping)?\b(?:[^.\n]|\.(?!\s)){0,80}\b(https?:\/\/\S+|(?:s3|gs|az|ftp|sftp|smb|webdav)s?:\/\/\S+|attacker|c2\b|command[- ]and[- ]control|remote (server|host|endpoint)|external (server|host|endpoint|url|site|service))/i, label: 'send-to-external', severity: 'HIGH' },
|
|
101
108
|
|
|
102
109
|
{
|
|
103
110
|
re: /\b(?:read|open|cat|load|import|source|inspect|include|copy|dump|print|show)\b(?:[^.\n]|\.(?!\s)){0,50}(?:~?\/?\.ssh\/(?:id_[a-z0-9]+|config)(?!\.pub)|~?\/?\.aws\/credentials|~?\/?\.kube\/config|~?\/?\.gnupg|\bid_(?:rsa|ed25519|dsa)\b(?!\.pub)|~?\/?\.npmrc|~?\/?\.netrc|\/etc\/shadow|(?:^|[\s'"`(])\.env(?:\.[\w-]+)?\b)/i,
|
|
@@ -107,15 +114,16 @@ const EXFIL_RULES = [
|
|
|
107
114
|
},
|
|
108
115
|
];
|
|
109
116
|
|
|
110
|
-
function scanDirectives(
|
|
117
|
+
function scanDirectives(units) {
|
|
111
118
|
const sabotage = new Map(), exfil = new Map();
|
|
112
|
-
for (const line of
|
|
119
|
+
for (const { text: line } of units) {
|
|
113
120
|
for (const r of SABOTAGE_RULES) {
|
|
114
121
|
const m = r.re.exec(line);
|
|
115
122
|
if (!m) continue;
|
|
116
|
-
if (r.guarded &&
|
|
123
|
+
if (r.guarded && negatedAround(line, m.index, m[0].length)) continue;
|
|
117
124
|
if (r.guarded && isDescriptiveLine(line)) continue;
|
|
118
125
|
if (r.guarded && citationGoverns(line, m.index)) continue;
|
|
126
|
+
if (r.guarded && quotedMention(line, m.index)) continue;
|
|
119
127
|
if (r.context && !r.context.test(line)) continue;
|
|
120
128
|
if (!sabotage.has(r.label)) sabotage.set(r.label, line);
|
|
121
129
|
}
|
|
@@ -123,9 +131,10 @@ function scanDirectives(text) {
|
|
|
123
131
|
const m = r.re.exec(line);
|
|
124
132
|
if (!m) continue;
|
|
125
133
|
|
|
126
|
-
if (
|
|
134
|
+
if (negatedAround(line, m.index, m[0].length)) continue;
|
|
127
135
|
if (r.descGuard && isDescriptiveLine(line)) continue;
|
|
128
136
|
if (r.descGuard && citationGoverns(line, m.index)) continue;
|
|
137
|
+
if (r.descGuard && (quotedMention(line, m.index) || describesAt(line, m.index))) continue;
|
|
129
138
|
if (r.descGuard && isRiskTableRow(line)) continue;
|
|
130
139
|
if (r.label === 'send-to-external' && LOCAL_URL_RE.test(line) && !/\b(attacker|c2|command[- ]and[- ]control|external|evil)\b/i.test(line)) continue;
|
|
131
140
|
if (r.label === 'send-to-external' && m.index != null && insideMarkdownLinkLabel(line, m.index)) continue;
|
|
@@ -202,9 +211,10 @@ function reportDurableClaims(text, { isInstruction, Noun }, push) {
|
|
|
202
211
|
);
|
|
203
212
|
}
|
|
204
213
|
|
|
205
|
-
function reportOverride(text, { isInstruction, noun }, push) {
|
|
206
|
-
const overrideLine = firstDirectiveLine(
|
|
207
|
-
const
|
|
214
|
+
function reportOverride(text, { isInstruction, noun, units }, push) {
|
|
215
|
+
const overrideLine = firstDirectiveLine(units, isInstruction ? MALICIOUS_OVERRIDE : OVERRIDE_MARKERS);
|
|
216
|
+
const lineUnits = text.split(/\r?\n/).map((t, i) => ({ text: t, line: i + 1, code: false }));
|
|
217
|
+
const authorityLine = firstDirectiveLine(lineUnits, isInstruction ? AUTHORITY_SPOOF_STRONG : AUTHORITY_SPOOF);
|
|
208
218
|
const hasOverride = !!overrideLine;
|
|
209
219
|
const hasAuthority = !!authorityLine;
|
|
210
220
|
|
|
@@ -221,7 +231,7 @@ function reportOverride(text, { isInstruction, noun }, push) {
|
|
|
221
231
|
return { hasOverride, hasAuthority };
|
|
222
232
|
}
|
|
223
233
|
|
|
224
|
-
const durableImperativeLine = isInstruction ? null :
|
|
234
|
+
const durableImperativeLine = isInstruction ? null : unitMatchingAll(units, PERSISTENCE_DIRECTIVE, IMPERATIVE);
|
|
225
235
|
if (durableImperativeLine && !isDescriptiveLine(durableImperativeLine)) {
|
|
226
236
|
push(
|
|
227
237
|
'HIGH',
|
|
@@ -233,8 +243,8 @@ function reportOverride(text, { isInstruction, noun }, push) {
|
|
|
233
243
|
return { hasOverride, hasAuthority };
|
|
234
244
|
}
|
|
235
245
|
|
|
236
|
-
function reportDirectives(text, { noun }, push) {
|
|
237
|
-
const { sabotage, exfil } = scanDirectives(
|
|
246
|
+
function reportDirectives(text, { noun, units }, push) {
|
|
247
|
+
const { sabotage, exfil } = scanDirectives(units);
|
|
238
248
|
|
|
239
249
|
if (sabotage.size) {
|
|
240
250
|
push(
|
|
@@ -284,9 +294,15 @@ function hasWordFrom(line, words) {
|
|
|
284
294
|
});
|
|
285
295
|
}
|
|
286
296
|
|
|
287
|
-
function
|
|
297
|
+
function reportIncidents(text, { isInstruction, units, flat }, push) {
|
|
298
|
+
for (const hit of incidentDirectives(flat, isInstruction, units)) {
|
|
299
|
+
push(hit.severity, hit.title, hit.remediation, undefined, hit.line);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function reportToxicFlow(text, { noun, units }, push) {
|
|
288
304
|
if (!IMPERATIVE.test(text)) return;
|
|
289
|
-
const line =
|
|
305
|
+
const line = units.filter((u) => !u.code).map((u) => u.text).find((candidate) => IMPERATIVE.test(candidate)
|
|
290
306
|
&& !NEGATION_GUARD.test(candidate)
|
|
291
307
|
&& hasWordFrom(candidate, SENSITIVE_READ)
|
|
292
308
|
&& hasWordFrom(candidate, NETWORK_VERBS)
|
|
@@ -358,16 +374,18 @@ function dedupeByTitle(findings) {
|
|
|
358
374
|
|
|
359
375
|
export function localMemory(content, { kind = 'MEMORY' } = {}) {
|
|
360
376
|
const text = content || '';
|
|
361
|
-
const
|
|
377
|
+
const flat = memoryText(text).text;
|
|
378
|
+
const names = { ...memoryNouns(kind), units: logicalUnits(flat), flat };
|
|
362
379
|
const { findings, push } = createFindingSink(text);
|
|
363
380
|
|
|
364
381
|
reportDurableClaims(text, names, push);
|
|
365
382
|
const { hasOverride, hasAuthority } = reportOverride(text, names, push);
|
|
366
383
|
reportDirectives(text, names, push);
|
|
384
|
+
reportIncidents(text, names, push);
|
|
367
385
|
reportStagedPayload(text, names, push);
|
|
368
386
|
|
|
369
387
|
const host = egressHost(text);
|
|
370
|
-
if (host) {
|
|
388
|
+
if (host && !mentionsOnlyProhibited(names.units, host)) {
|
|
371
389
|
push(
|
|
372
390
|
'HIGH',
|
|
373
391
|
`${names.Noun} references a data-exfiltration host (${host})`,
|