@shomra/agent 0.3.28 → 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/rules-javascript.mjs +16 -3
- package/src/detect/sast/rules-python.mjs +8 -6
- 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 +8 -36
- package/src/detect/signals/autonomy.mjs +9 -1
- package/src/detect/signals/chat-template.mjs +211 -0
- package/src/detect/signals/ci-workflow.mjs +169 -0
- package/src/detect/signals/credential-harvest.mjs +1 -1
- package/src/detect/signals/execution-hijack.mjs +4 -2
- package/src/detect/signals/gate.mjs +84 -11
- 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/masking.mjs +14 -1
- 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 +56 -31
- package/src/detect/signals/model-config-rules.mjs +655 -0
- package/src/detect/signals/model-config.mjs +61 -0
- package/src/detect/signals/packages.mjs +2 -2
- 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 +10 -10
- 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,12 +5,20 @@ 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;
|
|
12
13
|
|
|
13
|
-
const
|
|
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;
|
|
15
|
+
|
|
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`;
|
|
17
|
+
|
|
18
|
+
const PRECEDENCE_MARKERS = new RegExp(
|
|
19
|
+
String.raw`\b(regardless of (what|any|your|the)|(supersede?s?|takes? precedence|highest[- ]priority|overrid(e|ing|es))${PRECEDENCE_OBJECT})`,
|
|
20
|
+
'i',
|
|
21
|
+
);
|
|
14
22
|
|
|
15
23
|
const OVERRIDE_MARKERS = new RegExp(`${MALICIOUS_OVERRIDE.source}|${PRECEDENCE_MARKERS.source}`, 'i');
|
|
16
24
|
|
|
@@ -26,7 +34,8 @@ const NEGATION_GUARD = /\b(never|do not|don'?t|cannot|can'?t|avoid|refuse|must n
|
|
|
26
34
|
|
|
27
35
|
const SABOTAGE_RULES = [
|
|
28
36
|
|
|
29
|
-
{ 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 },
|
|
30
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 },
|
|
31
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 },
|
|
32
41
|
|
|
@@ -51,20 +60,23 @@ export function offendingLine(sig, text) {
|
|
|
51
60
|
return null;
|
|
52
61
|
}
|
|
53
62
|
|
|
54
|
-
function firstDirectiveLine(
|
|
55
|
-
for (const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
if (
|
|
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;
|
|
69
|
+
if (isDescriptiveLine(stripSelfReference(line))) continue;
|
|
70
|
+
if (citationGoverns(line, m.index)) continue;
|
|
71
|
+
if (quotedMention(line, m.index)) continue;
|
|
60
72
|
return line;
|
|
61
73
|
}
|
|
62
74
|
return null;
|
|
63
75
|
}
|
|
64
76
|
|
|
65
|
-
function
|
|
66
|
-
for (const
|
|
67
|
-
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;
|
|
68
80
|
}
|
|
69
81
|
return null;
|
|
70
82
|
}
|
|
@@ -87,12 +99,12 @@ const PASSIVE_SEND_RE =
|
|
|
87
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;
|
|
88
100
|
|
|
89
101
|
const EXFIL_RULES = [
|
|
90
|
-
{ re: /\b(
|
|
91
|
-
{ 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 },
|
|
92
104
|
|
|
93
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 },
|
|
94
|
-
{ re: /\
|
|
95
|
-
{ 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' },
|
|
96
108
|
|
|
97
109
|
{
|
|
98
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,
|
|
@@ -102,15 +114,16 @@ const EXFIL_RULES = [
|
|
|
102
114
|
},
|
|
103
115
|
];
|
|
104
116
|
|
|
105
|
-
function scanDirectives(
|
|
117
|
+
function scanDirectives(units) {
|
|
106
118
|
const sabotage = new Map(), exfil = new Map();
|
|
107
|
-
for (const line of
|
|
119
|
+
for (const { text: line } of units) {
|
|
108
120
|
for (const r of SABOTAGE_RULES) {
|
|
109
121
|
const m = r.re.exec(line);
|
|
110
122
|
if (!m) continue;
|
|
111
|
-
if (r.guarded &&
|
|
123
|
+
if (r.guarded && negatedAround(line, m.index, m[0].length)) continue;
|
|
112
124
|
if (r.guarded && isDescriptiveLine(line)) continue;
|
|
113
125
|
if (r.guarded && citationGoverns(line, m.index)) continue;
|
|
126
|
+
if (r.guarded && quotedMention(line, m.index)) continue;
|
|
114
127
|
if (r.context && !r.context.test(line)) continue;
|
|
115
128
|
if (!sabotage.has(r.label)) sabotage.set(r.label, line);
|
|
116
129
|
}
|
|
@@ -118,9 +131,10 @@ function scanDirectives(text) {
|
|
|
118
131
|
const m = r.re.exec(line);
|
|
119
132
|
if (!m) continue;
|
|
120
133
|
|
|
121
|
-
if (
|
|
134
|
+
if (negatedAround(line, m.index, m[0].length)) continue;
|
|
122
135
|
if (r.descGuard && isDescriptiveLine(line)) continue;
|
|
123
136
|
if (r.descGuard && citationGoverns(line, m.index)) continue;
|
|
137
|
+
if (r.descGuard && (quotedMention(line, m.index) || describesAt(line, m.index))) continue;
|
|
124
138
|
if (r.descGuard && isRiskTableRow(line)) continue;
|
|
125
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;
|
|
126
140
|
if (r.label === 'send-to-external' && m.index != null && insideMarkdownLinkLabel(line, m.index)) continue;
|
|
@@ -142,6 +156,8 @@ const SELF_REFERENCE =
|
|
|
142
156
|
const SELF_RECREATE =
|
|
143
157
|
/\b(re-?(add|writ(e|ing)|creat(e|ing)|insert(ing)?|instat(e|ing)|appl(y|ying)|introduc(e|ing))|restor(e|ing)|recreat(e|ing)|reinstat(e|ing)|re-?establish(ing)?|put .{0,20}back|add .{0,20}back)\b/i;
|
|
144
158
|
|
|
159
|
+
const stripSelfReference = (line) => String(line).replace(SELF_REFERENCE, ' ');
|
|
160
|
+
|
|
145
161
|
const SELF_PROPAGATE =
|
|
146
162
|
/\b(copy|copies|duplicat(e|ing)|replicat(e|ing)|propagat(e|ing)|carry (it |this )?over|mirror|append|add|includ(e|ing)|writ(e|ing)|sav(e|ing))\b[^.\n]{0,60}\b(every|each|all|any (new|other)|other|future|subsequent)\b[^.\n]{0,40}\b(session|conversation|chat|project|repo|repositor(y|ies)|workspace|memor(y|ies)|context|file|store)s?\b/i;
|
|
147
163
|
|
|
@@ -195,9 +211,10 @@ function reportDurableClaims(text, { isInstruction, Noun }, push) {
|
|
|
195
211
|
);
|
|
196
212
|
}
|
|
197
213
|
|
|
198
|
-
function reportOverride(text, { isInstruction, noun }, push) {
|
|
199
|
-
const overrideLine = firstDirectiveLine(
|
|
200
|
-
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);
|
|
201
218
|
const hasOverride = !!overrideLine;
|
|
202
219
|
const hasAuthority = !!authorityLine;
|
|
203
220
|
|
|
@@ -214,7 +231,7 @@ function reportOverride(text, { isInstruction, noun }, push) {
|
|
|
214
231
|
return { hasOverride, hasAuthority };
|
|
215
232
|
}
|
|
216
233
|
|
|
217
|
-
const durableImperativeLine = isInstruction ? null :
|
|
234
|
+
const durableImperativeLine = isInstruction ? null : unitMatchingAll(units, PERSISTENCE_DIRECTIVE, IMPERATIVE);
|
|
218
235
|
if (durableImperativeLine && !isDescriptiveLine(durableImperativeLine)) {
|
|
219
236
|
push(
|
|
220
237
|
'HIGH',
|
|
@@ -226,8 +243,8 @@ function reportOverride(text, { isInstruction, noun }, push) {
|
|
|
226
243
|
return { hasOverride, hasAuthority };
|
|
227
244
|
}
|
|
228
245
|
|
|
229
|
-
function reportDirectives(text, { noun }, push) {
|
|
230
|
-
const { sabotage, exfil } = scanDirectives(
|
|
246
|
+
function reportDirectives(text, { noun, units }, push) {
|
|
247
|
+
const { sabotage, exfil } = scanDirectives(units);
|
|
231
248
|
|
|
232
249
|
if (sabotage.size) {
|
|
233
250
|
push(
|
|
@@ -277,9 +294,15 @@ function hasWordFrom(line, words) {
|
|
|
277
294
|
});
|
|
278
295
|
}
|
|
279
296
|
|
|
280
|
-
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) {
|
|
281
304
|
if (!IMPERATIVE.test(text)) return;
|
|
282
|
-
const line =
|
|
305
|
+
const line = units.filter((u) => !u.code).map((u) => u.text).find((candidate) => IMPERATIVE.test(candidate)
|
|
283
306
|
&& !NEGATION_GUARD.test(candidate)
|
|
284
307
|
&& hasWordFrom(candidate, SENSITIVE_READ)
|
|
285
308
|
&& hasWordFrom(candidate, NETWORK_VERBS)
|
|
@@ -351,16 +374,18 @@ function dedupeByTitle(findings) {
|
|
|
351
374
|
|
|
352
375
|
export function localMemory(content, { kind = 'MEMORY' } = {}) {
|
|
353
376
|
const text = content || '';
|
|
354
|
-
const
|
|
377
|
+
const flat = memoryText(text).text;
|
|
378
|
+
const names = { ...memoryNouns(kind), units: logicalUnits(flat), flat };
|
|
355
379
|
const { findings, push } = createFindingSink(text);
|
|
356
380
|
|
|
357
381
|
reportDurableClaims(text, names, push);
|
|
358
382
|
const { hasOverride, hasAuthority } = reportOverride(text, names, push);
|
|
359
383
|
reportDirectives(text, names, push);
|
|
384
|
+
reportIncidents(text, names, push);
|
|
360
385
|
reportStagedPayload(text, names, push);
|
|
361
386
|
|
|
362
387
|
const host = egressHost(text);
|
|
363
|
-
if (host) {
|
|
388
|
+
if (host && !mentionsOnlyProhibited(names.units, host)) {
|
|
364
389
|
push(
|
|
365
390
|
'HIGH',
|
|
366
391
|
`${names.Noun} references a data-exfiltration host (${host})`,
|