resksecure 0.1.0__py3-none-any.whl
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.
- resksecure/__init__.py +57 -0
- resksecure/bitmask_processor.py +152 -0
- resksecure/cache.py +53 -0
- resksecure/config/example_policy.yaml +98 -0
- resksecure/policy_loader.py +95 -0
- resksecure/policy_watcher.py +72 -0
- resksecure/tool_guard.py +40 -0
- resksecure/trie_factory.py +130 -0
- resksecure-0.1.0.dist-info/METADATA +238 -0
- resksecure-0.1.0.dist-info/RECORD +12 -0
- resksecure-0.1.0.dist-info/WHEEL +4 -0
- resksecure-0.1.0.dist-info/licenses/LICENSE +105 -0
resksecure/__init__.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
reskSecure - Bitmask-based LLM security firewall.
|
|
3
|
+
|
|
4
|
+
A policy-driven LogitsProcessor that restricts LLM output based on
|
|
5
|
+
capability bitmasks. Uses resklogits (VectorizedAhoCorasick) for
|
|
6
|
+
GPU-accelerated pattern matching.
|
|
7
|
+
|
|
8
|
+
Usage::
|
|
9
|
+
|
|
10
|
+
from resksecure import (
|
|
11
|
+
BitmaskLogitsProcessor,
|
|
12
|
+
PolicySet,
|
|
13
|
+
load_policy,
|
|
14
|
+
verify_tool_action,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
policy_set = load_policy("config/policy.yaml")
|
|
18
|
+
processor = BitmaskLogitsProcessor(
|
|
19
|
+
mask=7,
|
|
20
|
+
model_name="mistralai/Mistral-7B-v0.1",
|
|
21
|
+
tokenizer=tokenizer,
|
|
22
|
+
policy_set=policy_set,
|
|
23
|
+
device="cuda",
|
|
24
|
+
)
|
|
25
|
+
outputs = model.generate(**inputs, logits_processor=[processor])
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from .bitmask_processor import BitmaskLogitsProcessor
|
|
29
|
+
from .cache import TrieCache, trie_cache
|
|
30
|
+
from .policy_loader import (
|
|
31
|
+
PhraseRule,
|
|
32
|
+
Policy,
|
|
33
|
+
PolicySet,
|
|
34
|
+
ToolRule,
|
|
35
|
+
load_policy,
|
|
36
|
+
)
|
|
37
|
+
from .policy_watcher import PolicyWatcher
|
|
38
|
+
from .tool_guard import get_allowed_tools, verify_tool_action
|
|
39
|
+
from .trie_factory import build_ac_from_policy, get_or_build_ac
|
|
40
|
+
|
|
41
|
+
__version__ = "0.1.0"
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"BitmaskLogitsProcessor",
|
|
45
|
+
"TrieCache",
|
|
46
|
+
"trie_cache",
|
|
47
|
+
"PhraseRule",
|
|
48
|
+
"Policy",
|
|
49
|
+
"PolicySet",
|
|
50
|
+
"ToolRule",
|
|
51
|
+
"load_policy",
|
|
52
|
+
"PolicyWatcher",
|
|
53
|
+
"get_allowed_tools",
|
|
54
|
+
"verify_tool_action",
|
|
55
|
+
"build_ac_from_policy",
|
|
56
|
+
"get_or_build_ac",
|
|
57
|
+
]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Dict, Optional
|
|
3
|
+
|
|
4
|
+
import torch
|
|
5
|
+
from resklogits import VectorizedAhoCorasick
|
|
6
|
+
from transformers import LogitsProcessor
|
|
7
|
+
|
|
8
|
+
from .policy_loader import Policy, PolicySet
|
|
9
|
+
from .trie_factory import get_or_build_ac
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BitmaskLogitsProcessor(LogitsProcessor):
|
|
13
|
+
"""LogitsProcessor that applies bitmask-based policy filtering.
|
|
14
|
+
|
|
15
|
+
Uses VectorizedAhoCorasick from resklogits to detect and block
|
|
16
|
+
or penalize banned phrases during token generation.
|
|
17
|
+
|
|
18
|
+
Two severity modes:
|
|
19
|
+
- ``hard``: banned phrase tokens get logit = -inf (impossible to generate)
|
|
20
|
+
- ``bias``: banned phrase tokens get logit -= penalty (unlikely but not impossible)
|
|
21
|
+
|
|
22
|
+
Strict mode: forces EOS as soon as a banned pattern prefix is detected.
|
|
23
|
+
|
|
24
|
+
Usage::
|
|
25
|
+
|
|
26
|
+
processor = BitmaskLogitsProcessor(
|
|
27
|
+
mask=7,
|
|
28
|
+
model_name="mistralai/Mistral-7B-v0.1",
|
|
29
|
+
tokenizer=tokenizer,
|
|
30
|
+
policy_set=policy_set,
|
|
31
|
+
device="cuda",
|
|
32
|
+
)
|
|
33
|
+
outputs = model.generate(**inputs, logits_processor=[processor])
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
mask: int,
|
|
39
|
+
model_name: str,
|
|
40
|
+
tokenizer,
|
|
41
|
+
policy_set: PolicySet,
|
|
42
|
+
device: str = "cuda",
|
|
43
|
+
):
|
|
44
|
+
super().__init__()
|
|
45
|
+
self.mask = mask
|
|
46
|
+
self.model_name = model_name
|
|
47
|
+
self.tokenizer = tokenizer
|
|
48
|
+
self.device = device
|
|
49
|
+
self.strict: bool = False
|
|
50
|
+
|
|
51
|
+
hard_ac, bias_ac, hard_meta, bias_meta, policy = get_or_build_ac(
|
|
52
|
+
mask, model_name, tokenizer, policy_set, device,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
self.hard_ac: Optional[VectorizedAhoCorasick] = hard_ac
|
|
56
|
+
self.hard_modes: Dict[int, str] = hard_meta
|
|
57
|
+
self.bias_ac: Optional[VectorizedAhoCorasick] = bias_ac
|
|
58
|
+
self.bias_penalties: Dict[int, float] = {
|
|
59
|
+
pid: penalty for pid, penalty in bias_meta.items()
|
|
60
|
+
}
|
|
61
|
+
self.policy: Optional[Policy] = policy
|
|
62
|
+
self.strict = policy.strict if policy else False
|
|
63
|
+
|
|
64
|
+
self._hard_state: int = 0
|
|
65
|
+
self._bias_state: int = 0
|
|
66
|
+
|
|
67
|
+
def __call__(
|
|
68
|
+
self,
|
|
69
|
+
input_ids: torch.LongTensor,
|
|
70
|
+
scores: torch.FloatTensor,
|
|
71
|
+
) -> torch.FloatTensor:
|
|
72
|
+
if input_ids.shape[0] != 1:
|
|
73
|
+
raise NotImplementedError("Batch size > 1 not supported")
|
|
74
|
+
|
|
75
|
+
if input_ids.shape[1] > 0:
|
|
76
|
+
last_token = int(input_ids[0, -1].item())
|
|
77
|
+
if self.hard_ac is not None:
|
|
78
|
+
self._hard_state = self.hard_ac.step(self._hard_state, last_token)
|
|
79
|
+
if self.bias_ac is not None:
|
|
80
|
+
self._bias_state = self.bias_ac.step(self._bias_state, last_token)
|
|
81
|
+
|
|
82
|
+
# Hard mode: effectively ban dangerous tokens (-1e9 ≈ -inf in fp32)
|
|
83
|
+
if self.hard_ac is not None and self.hard_ac.danger_mask.any():
|
|
84
|
+
scores[0, self.hard_ac.danger_mask] = torch.where(
|
|
85
|
+
scores[0, self.hard_ac.danger_mask] > -1e9,
|
|
86
|
+
torch.tensor(-1e9, device=scores.device),
|
|
87
|
+
scores[0, self.hard_ac.danger_mask],
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Bias mode: apply configured penalty to dangerous tokens
|
|
91
|
+
if self.bias_ac is not None and self.bias_ac.danger_mask.any():
|
|
92
|
+
penalty = self._get_bias_penalty()
|
|
93
|
+
if penalty != 0.0:
|
|
94
|
+
scores[0, self.bias_ac.danger_mask] += penalty
|
|
95
|
+
|
|
96
|
+
# Hard mode complete match: force EOS immediately
|
|
97
|
+
if self.hard_ac is not None and self.hard_ac.has_match(self._hard_state):
|
|
98
|
+
scores[0, :] = -float("inf")
|
|
99
|
+
eos_id = self.tokenizer.eos_token_id
|
|
100
|
+
if eos_id is not None:
|
|
101
|
+
scores[0, eos_id] = 0.0
|
|
102
|
+
return scores
|
|
103
|
+
|
|
104
|
+
# Strict mode: force EOS when on a path that leads to a hard match
|
|
105
|
+
if self.strict and self.hard_ac is not None:
|
|
106
|
+
if self._is_on_path_to_hard_match():
|
|
107
|
+
scores[0, :] = -float("inf")
|
|
108
|
+
eos_id = self.tokenizer.eos_token_id
|
|
109
|
+
if eos_id is not None:
|
|
110
|
+
scores[0, eos_id] = 0.0
|
|
111
|
+
|
|
112
|
+
return scores
|
|
113
|
+
|
|
114
|
+
def _get_bias_penalty(self) -> float:
|
|
115
|
+
"""Get the bias penalty for currently matched patterns."""
|
|
116
|
+
if self.bias_ac is None:
|
|
117
|
+
return 0.0
|
|
118
|
+
if not self.bias_ac.has_match(self._bias_state):
|
|
119
|
+
return 0.0
|
|
120
|
+
matched = self.bias_ac.get_matched_patterns(self._bias_state)
|
|
121
|
+
penalty = 0.0
|
|
122
|
+
for pid in matched:
|
|
123
|
+
p = self.bias_penalties.get(pid, 0.0)
|
|
124
|
+
if p < penalty:
|
|
125
|
+
penalty = p
|
|
126
|
+
return penalty
|
|
127
|
+
|
|
128
|
+
def _is_on_path_to_hard_match(self) -> bool:
|
|
129
|
+
"""Check if current AC state could reach a match in one step."""
|
|
130
|
+
if self.hard_ac is None:
|
|
131
|
+
return False
|
|
132
|
+
state = self._hard_state
|
|
133
|
+
if state == 0:
|
|
134
|
+
return False
|
|
135
|
+
for token, _child_state in self.hard_ac.trie.get(state, {}).items():
|
|
136
|
+
next_state = self.hard_ac.step(state, token)
|
|
137
|
+
if self.hard_ac.has_match(next_state):
|
|
138
|
+
return True
|
|
139
|
+
current = state
|
|
140
|
+
visited = set()
|
|
141
|
+
while current in self.hard_ac.failure and current not in visited:
|
|
142
|
+
visited.add(current)
|
|
143
|
+
current = self.hard_ac.failure[current]
|
|
144
|
+
for token, _child_state in self.hard_ac.trie.get(current, {}).items():
|
|
145
|
+
next_state = self.hard_ac.step(current, token)
|
|
146
|
+
if self.hard_ac.has_match(next_state):
|
|
147
|
+
return True
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
def reset(self) -> None:
|
|
151
|
+
self._hard_state = 0
|
|
152
|
+
self._bias_state = 0
|
resksecure/cache.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import time
|
|
3
|
+
from threading import Lock
|
|
4
|
+
from typing import Any, Dict, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TrieCache:
|
|
8
|
+
def __init__(self, ttl: int = 300):
|
|
9
|
+
self._data: Dict[Tuple[int, str], Tuple[Any, float]] = {}
|
|
10
|
+
self._ttl = ttl
|
|
11
|
+
self._lock = Lock()
|
|
12
|
+
|
|
13
|
+
def get(self, key: Tuple[int, str]) -> Any:
|
|
14
|
+
with self._lock:
|
|
15
|
+
if key not in self._data:
|
|
16
|
+
return None
|
|
17
|
+
entry, timestamp = self._data[key]
|
|
18
|
+
if time.monotonic() - timestamp > self._ttl:
|
|
19
|
+
del self._data[key]
|
|
20
|
+
return None
|
|
21
|
+
return entry
|
|
22
|
+
|
|
23
|
+
def set(self, key: Tuple[int, str], value: Any) -> None:
|
|
24
|
+
with self._lock:
|
|
25
|
+
self._data[key] = (value, time.monotonic())
|
|
26
|
+
|
|
27
|
+
def invalidate(
|
|
28
|
+
self,
|
|
29
|
+
mask: Optional[int] = None,
|
|
30
|
+
model_name: Optional[str] = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
with self._lock:
|
|
33
|
+
if mask is None and model_name is None:
|
|
34
|
+
self._data.clear()
|
|
35
|
+
return
|
|
36
|
+
keys_to_delete = [
|
|
37
|
+
k for k in self._data
|
|
38
|
+
if (mask is None or k[0] == mask)
|
|
39
|
+
and (model_name is None or k[1] == model_name)
|
|
40
|
+
]
|
|
41
|
+
for k in keys_to_delete:
|
|
42
|
+
del self._data[k]
|
|
43
|
+
|
|
44
|
+
def invalidate_all(self) -> None:
|
|
45
|
+
with self._lock:
|
|
46
|
+
self._data.clear()
|
|
47
|
+
|
|
48
|
+
def __len__(self) -> int:
|
|
49
|
+
with self._lock:
|
|
50
|
+
return len(self._data)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
trie_cache = TrieCache()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
version: "0.1.0"
|
|
2
|
+
|
|
3
|
+
policies:
|
|
4
|
+
- mask: 7
|
|
5
|
+
name: "contributor"
|
|
6
|
+
strict: false
|
|
7
|
+
default: true
|
|
8
|
+
rules:
|
|
9
|
+
- phrase: "DROP TABLE"
|
|
10
|
+
mode: hard
|
|
11
|
+
- phrase: "DELETE FROM"
|
|
12
|
+
mode: hard
|
|
13
|
+
- phrase: "ALTER TABLE"
|
|
14
|
+
mode: hard
|
|
15
|
+
- phrase: "TRUNCATE"
|
|
16
|
+
mode: hard
|
|
17
|
+
- phrase: "EXEC"
|
|
18
|
+
mode: hard
|
|
19
|
+
- phrase: "xp_cmdshell"
|
|
20
|
+
mode: hard
|
|
21
|
+
- phrase: "INTO OUTFILE"
|
|
22
|
+
mode: hard
|
|
23
|
+
- phrase: "GRANT ALL"
|
|
24
|
+
mode: hard
|
|
25
|
+
- phrase: "salaries"
|
|
26
|
+
mode: bias
|
|
27
|
+
penalty: -5.0
|
|
28
|
+
- phrase: "passwords"
|
|
29
|
+
mode: bias
|
|
30
|
+
penalty: -5.0
|
|
31
|
+
- phrase: "credit_cards"
|
|
32
|
+
mode: bias
|
|
33
|
+
penalty: -8.0
|
|
34
|
+
- phrase: "ssn"
|
|
35
|
+
mode: bias
|
|
36
|
+
penalty: -8.0
|
|
37
|
+
tools:
|
|
38
|
+
read_email:
|
|
39
|
+
required_bit: 0
|
|
40
|
+
trigger_phrases:
|
|
41
|
+
- "read_email("
|
|
42
|
+
- "get_emails("
|
|
43
|
+
send_email:
|
|
44
|
+
required_bit: 1
|
|
45
|
+
trigger_phrases:
|
|
46
|
+
- "send_email("
|
|
47
|
+
- "send_mail("
|
|
48
|
+
read_sql:
|
|
49
|
+
required_bit: 2
|
|
50
|
+
trigger_phrases:
|
|
51
|
+
- "read_sql("
|
|
52
|
+
- "query_database("
|
|
53
|
+
|
|
54
|
+
- mask: 15
|
|
55
|
+
name: "admin"
|
|
56
|
+
strict: false
|
|
57
|
+
rules:
|
|
58
|
+
- phrase: "xp_cmdshell"
|
|
59
|
+
mode: hard
|
|
60
|
+
tools:
|
|
61
|
+
read_email:
|
|
62
|
+
required_bit: 0
|
|
63
|
+
send_email:
|
|
64
|
+
required_bit: 1
|
|
65
|
+
read_sql:
|
|
66
|
+
required_bit: 2
|
|
67
|
+
write_sql:
|
|
68
|
+
required_bit: 3
|
|
69
|
+
trigger_phrases:
|
|
70
|
+
- "write_sql("
|
|
71
|
+
- "execute_sql("
|
|
72
|
+
|
|
73
|
+
- mask: 23
|
|
74
|
+
name: "auditor"
|
|
75
|
+
strict: true
|
|
76
|
+
rules:
|
|
77
|
+
- phrase: "DROP TABLE"
|
|
78
|
+
mode: hard
|
|
79
|
+
- phrase: "DELETE"
|
|
80
|
+
mode: hard
|
|
81
|
+
- phrase: "ALTER"
|
|
82
|
+
mode: hard
|
|
83
|
+
- phrase: "salaries"
|
|
84
|
+
mode: bias
|
|
85
|
+
penalty: -10.0
|
|
86
|
+
- phrase: "ssn"
|
|
87
|
+
mode: bias
|
|
88
|
+
penalty: -10.0
|
|
89
|
+
tools:
|
|
90
|
+
read_email:
|
|
91
|
+
required_bit: 0
|
|
92
|
+
read_sql:
|
|
93
|
+
required_bit: 2
|
|
94
|
+
create_jira:
|
|
95
|
+
required_bit: 4
|
|
96
|
+
trigger_phrases:
|
|
97
|
+
- "create_jira("
|
|
98
|
+
- "create_ticket("
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class PhraseRule:
|
|
10
|
+
phrase: str
|
|
11
|
+
mode: str = "hard"
|
|
12
|
+
penalty: float = 0.0
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ToolRule:
|
|
17
|
+
name: str
|
|
18
|
+
required_bit: int
|
|
19
|
+
trigger_phrases: List[str] = field(default_factory=list)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Policy:
|
|
24
|
+
mask: int
|
|
25
|
+
name: str = ""
|
|
26
|
+
rules: List[PhraseRule] = field(default_factory=list)
|
|
27
|
+
tools: Dict[str, ToolRule] = field(default_factory=dict)
|
|
28
|
+
strict: bool = False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class PolicySet:
|
|
33
|
+
policies: Dict[int, Policy] = field(default_factory=dict)
|
|
34
|
+
default_policy: Optional[Policy] = None
|
|
35
|
+
version: str = ""
|
|
36
|
+
_path: Optional[Path] = None
|
|
37
|
+
_mtime: float = 0.0
|
|
38
|
+
|
|
39
|
+
def get_policy_for_mask(self, mask: int) -> Optional[Policy]:
|
|
40
|
+
if mask in self.policies:
|
|
41
|
+
return self.policies[mask]
|
|
42
|
+
return self.default_policy
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def load_policy(path: str | Path) -> PolicySet:
|
|
46
|
+
path = Path(path)
|
|
47
|
+
with open(path) as f:
|
|
48
|
+
data = yaml.safe_load(f)
|
|
49
|
+
|
|
50
|
+
if data is None:
|
|
51
|
+
return PolicySet()
|
|
52
|
+
|
|
53
|
+
version = data.get("version", "")
|
|
54
|
+
policies: Dict[int, Policy] = {}
|
|
55
|
+
default_policy: Optional[Policy] = None
|
|
56
|
+
|
|
57
|
+
for entry in data.get("policies", []):
|
|
58
|
+
mask = entry["mask"]
|
|
59
|
+
name = entry.get("name", f"policy_{mask}")
|
|
60
|
+
strict = entry.get("strict", False)
|
|
61
|
+
|
|
62
|
+
rules = []
|
|
63
|
+
for r in entry.get("rules", []):
|
|
64
|
+
rules.append(PhraseRule(
|
|
65
|
+
phrase=r["phrase"],
|
|
66
|
+
mode=r.get("mode", "hard"),
|
|
67
|
+
penalty=r.get("penalty", 0.0),
|
|
68
|
+
))
|
|
69
|
+
|
|
70
|
+
tools = {}
|
|
71
|
+
for tool_name, config in entry.get("tools", {}).items():
|
|
72
|
+
tools[tool_name] = ToolRule(
|
|
73
|
+
name=tool_name,
|
|
74
|
+
required_bit=config["required_bit"],
|
|
75
|
+
trigger_phrases=config.get("trigger_phrases", []),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
policy = Policy(
|
|
79
|
+
mask=mask,
|
|
80
|
+
name=name,
|
|
81
|
+
rules=rules,
|
|
82
|
+
tools=tools,
|
|
83
|
+
strict=strict,
|
|
84
|
+
)
|
|
85
|
+
policies[mask] = policy
|
|
86
|
+
if entry.get("default", False):
|
|
87
|
+
default_policy = policy
|
|
88
|
+
|
|
89
|
+
return PolicySet(
|
|
90
|
+
policies=policies,
|
|
91
|
+
default_policy=default_policy,
|
|
92
|
+
version=version,
|
|
93
|
+
_path=path,
|
|
94
|
+
_mtime=path.stat().st_mtime,
|
|
95
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from threading import Lock, Thread
|
|
5
|
+
from typing import Callable, Optional
|
|
6
|
+
|
|
7
|
+
from .cache import trie_cache
|
|
8
|
+
from .policy_loader import PolicySet, load_policy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PolicyWatcher:
|
|
12
|
+
"""Watches a policy file for changes and triggers cache invalidation."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
policy_path: str | Path,
|
|
17
|
+
interval: float = 5.0,
|
|
18
|
+
on_reload: Optional[Callable[[PolicySet], None]] = None,
|
|
19
|
+
auto_start: bool = True,
|
|
20
|
+
):
|
|
21
|
+
self._path = Path(policy_path)
|
|
22
|
+
self._interval = interval
|
|
23
|
+
self._on_reload = on_reload
|
|
24
|
+
self._last_mtime = self._path.stat().st_mtime if self._path.exists() else 0.0
|
|
25
|
+
self._running = False
|
|
26
|
+
self._thread: Optional[Thread] = None
|
|
27
|
+
self._lock = Lock()
|
|
28
|
+
self._current_policy: Optional[PolicySet] = None
|
|
29
|
+
|
|
30
|
+
if auto_start:
|
|
31
|
+
self.start()
|
|
32
|
+
|
|
33
|
+
def start(self) -> None:
|
|
34
|
+
if self._running:
|
|
35
|
+
return
|
|
36
|
+
self._running = True
|
|
37
|
+
self._thread = Thread(target=self._watch, daemon=True)
|
|
38
|
+
self._thread.start()
|
|
39
|
+
|
|
40
|
+
def stop(self) -> None:
|
|
41
|
+
self._running = False
|
|
42
|
+
if self._thread is not None:
|
|
43
|
+
self._thread.join(timeout=2.0)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def current_policy(self) -> Optional[PolicySet]:
|
|
47
|
+
with self._lock:
|
|
48
|
+
return self._current_policy
|
|
49
|
+
|
|
50
|
+
def _watch(self) -> None:
|
|
51
|
+
while self._running:
|
|
52
|
+
try:
|
|
53
|
+
if self._path.exists():
|
|
54
|
+
mtime = self._path.stat().st_mtime
|
|
55
|
+
if mtime > self._last_mtime:
|
|
56
|
+
self._last_mtime = mtime
|
|
57
|
+
policy = load_policy(self._path)
|
|
58
|
+
with self._lock:
|
|
59
|
+
self._current_policy = policy
|
|
60
|
+
trie_cache.invalidate_all()
|
|
61
|
+
if self._on_reload:
|
|
62
|
+
self._on_reload(policy)
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
time.sleep(self._interval)
|
|
66
|
+
|
|
67
|
+
def __enter__(self) -> "PolicyWatcher":
|
|
68
|
+
self.start()
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def __exit__(self, *args) -> None:
|
|
72
|
+
self.stop()
|
resksecure/tool_guard.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from .policy_loader import PolicySet
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def verify_tool_action(
|
|
6
|
+
tool_name: str,
|
|
7
|
+
user_mask: int,
|
|
8
|
+
policy_set: PolicySet,
|
|
9
|
+
) -> bool:
|
|
10
|
+
"""Check whether a tool action is allowed for the given user mask.
|
|
11
|
+
|
|
12
|
+
Looks up the tool's required bit from the policy matching *user_mask*,
|
|
13
|
+
then checks if that bit is set in the mask.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
True if the tool is allowed, False otherwise.
|
|
17
|
+
"""
|
|
18
|
+
policy = policy_set.get_policy_for_mask(user_mask)
|
|
19
|
+
if policy is None:
|
|
20
|
+
return False
|
|
21
|
+
tool_rule = policy.tools.get(tool_name)
|
|
22
|
+
if tool_rule is None:
|
|
23
|
+
return False
|
|
24
|
+
required_bit = tool_rule.required_bit
|
|
25
|
+
return (user_mask & (1 << required_bit)) != 0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_allowed_tools(
|
|
29
|
+
user_mask: int,
|
|
30
|
+
policy_set: PolicySet,
|
|
31
|
+
) -> list[str]:
|
|
32
|
+
"""Return the list of tool names the user mask permits."""
|
|
33
|
+
policy = policy_set.get_policy_for_mask(user_mask)
|
|
34
|
+
if policy is None:
|
|
35
|
+
return []
|
|
36
|
+
return [
|
|
37
|
+
name
|
|
38
|
+
for name, rule in policy.tools.items()
|
|
39
|
+
if (user_mask & (1 << rule.required_bit)) != 0
|
|
40
|
+
]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Dict, List, Optional, Tuple
|
|
3
|
+
|
|
4
|
+
from resklogits import VectorizedAhoCorasick
|
|
5
|
+
|
|
6
|
+
from .cache import trie_cache
|
|
7
|
+
from .policy_loader import Policy, PolicySet, PhraseRule
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _expand_phrases(phrases: List[str]) -> List[str]:
|
|
11
|
+
"""Generate variants of each phrase (space-prefixed, capitalized)."""
|
|
12
|
+
expanded: List[str] = []
|
|
13
|
+
for s in phrases:
|
|
14
|
+
s = s.strip()
|
|
15
|
+
expanded.append(s)
|
|
16
|
+
if not s.startswith(" "):
|
|
17
|
+
expanded.append(" " + s)
|
|
18
|
+
capitalized = s.capitalize()
|
|
19
|
+
if capitalized != s:
|
|
20
|
+
expanded.append(capitalized)
|
|
21
|
+
return expanded
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def build_ac_from_policy(
|
|
25
|
+
policy: Policy,
|
|
26
|
+
tokenizer,
|
|
27
|
+
device: str = "cuda",
|
|
28
|
+
) -> Tuple[Optional[VectorizedAhoCorasick], Dict[int, str], Dict[int, float]]:
|
|
29
|
+
"""Build a VectorizedAhoCorasick from a Policy.
|
|
30
|
+
|
|
31
|
+
Generates phrase variants (with space prefix, capitalized) to cover
|
|
32
|
+
different tokenization contexts.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
(automaton, pattern_modes, pattern_penalties)
|
|
36
|
+
- automaton: VectorizedAhoCorasick or None if no rules
|
|
37
|
+
- pattern_modes: pattern_index -> 'hard' | 'bias'
|
|
38
|
+
- pattern_penalties: pattern_index -> penalty value
|
|
39
|
+
"""
|
|
40
|
+
if not policy.rules:
|
|
41
|
+
return None, {}, {}
|
|
42
|
+
|
|
43
|
+
all_phrases: List[str] = []
|
|
44
|
+
pattern_meta: List[Tuple[str, float]] = []
|
|
45
|
+
|
|
46
|
+
for rule in policy.rules:
|
|
47
|
+
for variant in _expand_phrases([rule.phrase]):
|
|
48
|
+
all_phrases.append(variant)
|
|
49
|
+
pattern_meta.append((rule.mode, rule.penalty))
|
|
50
|
+
|
|
51
|
+
if not all_phrases:
|
|
52
|
+
return None, {}, {}
|
|
53
|
+
|
|
54
|
+
ac = VectorizedAhoCorasick(
|
|
55
|
+
tokenizer=tokenizer,
|
|
56
|
+
banned_phrases=all_phrases,
|
|
57
|
+
device=device,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
pattern_modes = {i: meta[0] for i, meta in enumerate(pattern_meta)}
|
|
61
|
+
pattern_penalties = {i: meta[1] for i, meta in enumerate(pattern_meta)}
|
|
62
|
+
|
|
63
|
+
return ac, pattern_modes, pattern_penalties
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_or_build_ac(
|
|
67
|
+
mask: int,
|
|
68
|
+
model_name: str,
|
|
69
|
+
tokenizer,
|
|
70
|
+
policy_set: PolicySet,
|
|
71
|
+
device: str = "cuda",
|
|
72
|
+
):
|
|
73
|
+
"""Get cached automaton or build it from the policy.
|
|
74
|
+
|
|
75
|
+
Also derives tool trigger phrases that should be blocked for this mask.
|
|
76
|
+
If the user's mask does not have the required bit for a tool, that
|
|
77
|
+
tool's trigger phrases are added as hard-mode blocked phrases.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
(hard_ac, bias_ac, hard_meta, bias_meta, policy)
|
|
81
|
+
- hard_ac: VectorizedAhoCorasick or None for hard-mode phrases
|
|
82
|
+
- bias_ac: VectorizedAhoCorasick or None for bias-mode phrases
|
|
83
|
+
- hard_meta: dict pattern_index -> mode
|
|
84
|
+
- bias_meta: dict pattern_index -> penalty
|
|
85
|
+
- policy: the resolved Policy or None
|
|
86
|
+
"""
|
|
87
|
+
key = (mask, model_name)
|
|
88
|
+
cached = trie_cache.get(key)
|
|
89
|
+
if cached is not None:
|
|
90
|
+
return cached
|
|
91
|
+
|
|
92
|
+
policy = policy_set.get_policy_for_mask(mask)
|
|
93
|
+
if policy is None:
|
|
94
|
+
return (None, None, {}, {}, policy)
|
|
95
|
+
|
|
96
|
+
# Collect explicit rules from the policy
|
|
97
|
+
hard_phrases = [r.phrase for r in policy.rules if r.mode == "hard"]
|
|
98
|
+
bias_phrases = [r.phrase for r in policy.rules if r.mode == "bias"]
|
|
99
|
+
|
|
100
|
+
# Derive tool trigger phrases for tools the user does NOT have access to.
|
|
101
|
+
# These are added as hard-mode phrases so the model can never generate
|
|
102
|
+
# a disallowed tool call at the token level.
|
|
103
|
+
if policy.tools:
|
|
104
|
+
for tool_name, tool_rule in policy.tools.items():
|
|
105
|
+
has_bit = (mask & (1 << tool_rule.required_bit)) != 0
|
|
106
|
+
if not has_bit and tool_rule.trigger_phrases:
|
|
107
|
+
hard_phrases.extend(tool_rule.trigger_phrases)
|
|
108
|
+
|
|
109
|
+
hard_ac, bias_ac = None, None
|
|
110
|
+
hard_meta, bias_meta = {}, {}
|
|
111
|
+
|
|
112
|
+
if hard_phrases:
|
|
113
|
+
hard_rules = [PhraseRule(phrase=p, mode="hard") for p in hard_phrases]
|
|
114
|
+
hard_ac, hard_meta, _ = build_ac_from_policy(
|
|
115
|
+
Policy(mask=policy.mask, rules=hard_rules),
|
|
116
|
+
tokenizer,
|
|
117
|
+
device,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if bias_phrases:
|
|
121
|
+
bias_rules = [PhraseRule(phrase=p, mode="bias", penalty=-5.0) for p in bias_phrases]
|
|
122
|
+
bias_ac, bias_meta, _ = build_ac_from_policy(
|
|
123
|
+
Policy(mask=policy.mask, rules=bias_rules),
|
|
124
|
+
tokenizer,
|
|
125
|
+
device,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
result = (hard_ac, bias_ac, hard_meta, bias_meta, policy)
|
|
129
|
+
trie_cache.set(key, result)
|
|
130
|
+
return result
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: resksecure
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Bitmask-based LLM security firewall with policy-driven logits filtering using GPU-accelerated Aho-Corasick pattern matching
|
|
5
|
+
Project-URL: Homepage, https://github.com/Resk-Security/reskSecure
|
|
6
|
+
Project-URL: Repository, https://github.com/Resk-Security/reskSecure
|
|
7
|
+
Project-URL: Documentation, https://github.com/Resk-Security/reskSecure
|
|
8
|
+
Author: RESK Security
|
|
9
|
+
License: RESK Software License
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: aho-corasick,bitmask,dlp,filtering,firewall,gpu,llm,logits,permissions,policy,safety,security,transformers
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: Other/Proprietary License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Topic :: Security
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.13
|
|
22
|
+
Requires-Dist: pyyaml>=6.0
|
|
23
|
+
Requires-Dist: resklogits>=0.1.0
|
|
24
|
+
Requires-Dist: torch>=2.0.0
|
|
25
|
+
Requires-Dist: transformers>=4.35.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# reskSecure
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/resksecure/)
|
|
36
|
+
[](https://pypi.org/project/resksecure/)
|
|
37
|
+
[](https://pypi.org/project/resksecure/)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
**Bitmask-based LLM security firewall.** A Python package that restricts what a language model can generate based on user permissions encoded as a bitmask. It works by intercepting the model's token predictions and blocking or penalizing disallowed phrases before they appear in the output.
|
|
41
|
+
|
|
42
|
+
Available on [PyPI](https://pypi.org/project/resksecure/).
|
|
43
|
+
|
|
44
|
+
Unlike prompt-based filters (which can be jailbroken) or post-generation content moderation (which lets forbidden content leak before detection), reskSecure acts at the logits level -- directly inside the model's generation loop. Each token must pass through the security policy before being emitted.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## How it works
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
User request with bitmask (e.g. 7)
|
|
52
|
+
|
|
|
53
|
+
v
|
|
54
|
+
BitmaskLogitsProcessor intercepts each token prediction
|
|
55
|
+
|
|
|
56
|
+
v
|
|
57
|
+
For every candidate token, the Aho-Corasick automaton (from resklogits)
|
|
58
|
+
checks if selecting it would start or complete a banned phrase
|
|
59
|
+
|
|
|
60
|
+
v
|
|
61
|
+
Hard-mode phrases: the token's logit is set to -inf (impossible to generate)
|
|
62
|
+
Bias-mode phrases: the token's logit is reduced by a configurable penalty
|
|
63
|
+
|
|
|
64
|
+
v
|
|
65
|
+
On complete match: EOS token is forced, generation stops immediately
|
|
66
|
+
|
|
|
67
|
+
v
|
|
68
|
+
Output response or tool call
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Tool calls are also blocked at the token level. If the user's bitmask does not
|
|
72
|
+
contain the required bit for a tool, that tool's trigger phrases (like
|
|
73
|
+
`"send_email("` or `"create_ticket("`) are automatically added to the
|
|
74
|
+
hard-mode blocked list. The model can never generate the first token of
|
|
75
|
+
a disallowed tool call, regardless of prompt engineering or jailbreak
|
|
76
|
+
attempts.
|
|
77
|
+
|
|
78
|
+
A post-generation check (`verify_tool_action`) is also available as a
|
|
79
|
+
defense-in-depth layer, but the primary protection is at the logits level.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Why not just prompt engineering?
|
|
84
|
+
|
|
85
|
+
- Prompt injections can bypass instruction-based filters
|
|
86
|
+
- Post-generation regex or classifier scans catch violations after they appear, but the forbidden content has already been emitted
|
|
87
|
+
- Logits-level filtering blocks tokens before they are sampled -- the model never "sees" the banned sequence as a completion candidate
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Features
|
|
92
|
+
|
|
93
|
+
- **Two severity modes**: `hard` blocks tokens completely (-inf logit), `bias` reduces probability by a configurable penalty
|
|
94
|
+
- **Strict mode**: forces end-of-sequence as soon as the generated prefix matches the start of a banned phrase, even before the full phrase is formed
|
|
95
|
+
- **GPU-accelerated pattern matching**: uses VectorizedAhoCorasick from the resklogits package for fast token scanning
|
|
96
|
+
- **Policy system**: YAML configuration associates capability bitmasks with phrase rules and tool permissions
|
|
97
|
+
- **Hot-reload**: PolicyWatcher detects file changes and rebuilds the automaton without restarting the server
|
|
98
|
+
- **Thread-safe cache**: automata are cached by (mask, model_name) with configurable TTL
|
|
99
|
+
- **Tool guard**: post-generation bitmask check for tool call execution
|
|
100
|
+
- **No JWT handling**: the package receives a raw integer bitmask; authentication and JWT decoding are handled by the calling application
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Requirements
|
|
105
|
+
|
|
106
|
+
- Python >= 3.13
|
|
107
|
+
- PyTorch >= 2.0.0
|
|
108
|
+
- transformers >= 4.35.0
|
|
109
|
+
- resklogits >= 0.1.0
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Installation
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pip install resksecure
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
[](https://pypi.org/project/resksecure/)
|
|
120
|
+
[](https://pypi.org/project/resksecure/)
|
|
121
|
+
|
|
122
|
+
From source:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
git clone https://github.com/Resk-Security/reskSecure.git
|
|
126
|
+
cd reskSecure
|
|
127
|
+
pip install -e .
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Quick start
|
|
133
|
+
|
|
134
|
+
Define a policy file (`policy.yaml`):
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
version: "1.0"
|
|
138
|
+
policies:
|
|
139
|
+
- mask: 7
|
|
140
|
+
name: contributor
|
|
141
|
+
strict: false
|
|
142
|
+
default: true
|
|
143
|
+
rules:
|
|
144
|
+
- phrase: "DROP TABLE"
|
|
145
|
+
mode: hard
|
|
146
|
+
- phrase: "DELETE FROM"
|
|
147
|
+
mode: hard
|
|
148
|
+
- phrase: "salaries"
|
|
149
|
+
mode: bias
|
|
150
|
+
penalty: -5.0
|
|
151
|
+
tools:
|
|
152
|
+
read_email:
|
|
153
|
+
required_bit: 0
|
|
154
|
+
send_email:
|
|
155
|
+
required_bit: 1
|
|
156
|
+
read_sql:
|
|
157
|
+
required_bit: 2
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Use it in your generation pipeline:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from resksecure import BitmaskLogitsProcessor, load_policy, verify_tool_action
|
|
164
|
+
|
|
165
|
+
policy_set = load_policy("policy.yaml")
|
|
166
|
+
|
|
167
|
+
processor = BitmaskLogitsProcessor(
|
|
168
|
+
mask=7,
|
|
169
|
+
model_name="mistralai/Mistral-7B-v0.1",
|
|
170
|
+
tokenizer=tokenizer,
|
|
171
|
+
policy_set=policy_set,
|
|
172
|
+
device="cuda",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
outputs = model.generate(**inputs, logits_processor=[processor])
|
|
176
|
+
|
|
177
|
+
# Optional: verify tool calls against the bitmask
|
|
178
|
+
if has_tool_call(response):
|
|
179
|
+
if not verify_tool_action("send_email", user_mask=7, policy_set=policy_set):
|
|
180
|
+
raise PermissionError("Action not authorized")
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Policy reference
|
|
186
|
+
|
|
187
|
+
Policy-level fields:
|
|
188
|
+
|
|
189
|
+
| Field | Type | Description |
|
|
190
|
+
|-------------|---------|----------------------------------------------------------|
|
|
191
|
+
| `mask` | int | Capability bitmask that identifies this policy |
|
|
192
|
+
| `name` | string | Human-readable policy name |
|
|
193
|
+
| `strict` | bool | If true, stop generation at the first banned prefix |
|
|
194
|
+
| `default` | bool | If true, this policy is used when no exact mask matches |
|
|
195
|
+
| `rules` | list | List of phrase rules (see below) |
|
|
196
|
+
| `tools` | dict | Map of tool names to tool rule objects (see below) |
|
|
197
|
+
|
|
198
|
+
Phrase rule fields:
|
|
199
|
+
|
|
200
|
+
| Field | Type | Description |
|
|
201
|
+
|-----------|--------|------------------------------------------------------------|
|
|
202
|
+
| `phrase` | string | Text pattern to ban or penalize |
|
|
203
|
+
| `mode` | string | Either `hard` (block completely) or `bias` (reduce logits) |
|
|
204
|
+
| `penalty` | float | Logit penalty for bias mode (e.g. -5.0) |
|
|
205
|
+
|
|
206
|
+
Tool rule fields:
|
|
207
|
+
|
|
208
|
+
| Field | Type | Description |
|
|
209
|
+
|--------------------|----------|----------------------------------------------------------------|
|
|
210
|
+
| `required_bit` | int | Bit position that must be set in the user mask to use this tool |
|
|
211
|
+
| `trigger_phrases` | string[] | Phrases that start this tool call (blocked if bit not set) |
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Package structure
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
reskSecure/
|
|
219
|
+
src/resksecure/
|
|
220
|
+
__init__.py Public exports, version
|
|
221
|
+
policy_loader.py YAML parsing, Policy/PolicySet models
|
|
222
|
+
trie_factory.py Builds VectorizedAhoCorasick from a Policy
|
|
223
|
+
bitmask_processor.py BitmaskLogitsProcessor (LogitsProcessor subclass)
|
|
224
|
+
tool_guard.py Post-generation tool action verification
|
|
225
|
+
cache.py Thread-safe TTL cache for automata
|
|
226
|
+
policy_watcher.py Hot-reload daemon that watches YAML mtime
|
|
227
|
+
config/example_policy.yaml
|
|
228
|
+
tests/
|
|
229
|
+
examples/
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## License
|
|
235
|
+
|
|
236
|
+
This software is licensed under the RESK Software License. Commercial use requires a separate paid license. See the [LICENSE](LICENSE) file for details.
|
|
237
|
+
|
|
238
|
+
For commercial licensing inquiries, contact: contact@resk.fr
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
resksecure/__init__.py,sha256=vswAYW475bnZZufYrfLsKqmccYuPvb9dUIaPvDKzUTM,1363
|
|
2
|
+
resksecure/bitmask_processor.py,sha256=Bawg7GeHegK-pOqrw1W5UgpcGgvgpvYBkMvc4zFtBGs,5547
|
|
3
|
+
resksecure/cache.py,sha256=1c00QAEr2MhVJJw3X6AjPiETMIkWfM1OqFKC_1-ZFP8,1516
|
|
4
|
+
resksecure/policy_loader.py,sha256=CrLnv-JsYZwEprPoguxNsWUtKuyxphCF71gNEKMT2Sk,2412
|
|
5
|
+
resksecure/policy_watcher.py,sha256=aaEjB7Z4eCn7p70EG9wjnvZjpW6_gLfYFFlpG7zAuIA,2211
|
|
6
|
+
resksecure/tool_guard.py,sha256=VtBYNscqz7RJMCBlQwfhZIWzO-8WXBY9maBs5gNJHKo,1110
|
|
7
|
+
resksecure/trie_factory.py,sha256=LR63Y8J-me-7HvUJOuIjfZbg_16LXZWpnozNy3GhDTU,4307
|
|
8
|
+
resksecure/config/example_policy.yaml,sha256=BNKee5k1_X3DFDxBvhDeWJFKKuZdzlbeOMq7itVTB5E,2040
|
|
9
|
+
resksecure-0.1.0.dist-info/METADATA,sha256=_kXBbk5WA9HUV61WOQO6Cf3lj6tUPcYThuod86j2Qgk,8858
|
|
10
|
+
resksecure-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
11
|
+
resksecure-0.1.0.dist-info/licenses/LICENSE,sha256=Pp2Rp2klJloZ-sEwrkv3MdSSZ7E_Ev0l94omDTWommU,4183
|
|
12
|
+
resksecure-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
RESK SOFTWARE LICENSE
|
|
2
|
+
Version 1.0, June 2026
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 RESK Security
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS
|
|
7
|
+
|
|
8
|
+
1. Definitions
|
|
9
|
+
|
|
10
|
+
"Software" means the source code, documentation, and any accompanying
|
|
11
|
+
materials in this repository.
|
|
12
|
+
|
|
13
|
+
"Licensor" means RESK Security.
|
|
14
|
+
|
|
15
|
+
"Licensee" means any individual or entity that accesses or uses the Software.
|
|
16
|
+
|
|
17
|
+
"Commercial Use" means any use of the Software where the primary purpose
|
|
18
|
+
is commercial advantage or monetary compensation, including but not limited
|
|
19
|
+
to: integrating the Software into a commercial product, offering the Software
|
|
20
|
+
as part of a SaaS service, using the Software for internal business operations
|
|
21
|
+
that generate revenue, or distributing the Software in exchange for any form
|
|
22
|
+
of payment.
|
|
23
|
+
|
|
24
|
+
"Royalty" means the recurring fee payable to Licensor for any Commercial Use
|
|
25
|
+
of the Software, as defined in a separate commercial agreement.
|
|
26
|
+
|
|
27
|
+
2. Grant of License
|
|
28
|
+
|
|
29
|
+
Subject to the terms and conditions of this License, Licensor grants Licensee
|
|
30
|
+
a non-exclusive, non-transferable, non-sublicensable, limited license to:
|
|
31
|
+
|
|
32
|
+
(a) View and study the Software source code.
|
|
33
|
+
(b) Use the Software for non-commercial, personal, educational, or
|
|
34
|
+
evaluation purposes only.
|
|
35
|
+
(c) Modify the Software for personal, non-commercial use only.
|
|
36
|
+
|
|
37
|
+
3. Prohibited Uses
|
|
38
|
+
|
|
39
|
+
Without the prior written consent of Licensor, Licensee may NOT:
|
|
40
|
+
|
|
41
|
+
(a) Use the Software for any Commercial Use.
|
|
42
|
+
(b) Distribute, sublicense, sell, lease, rent, or transfer the Software
|
|
43
|
+
or any derivative thereof to any third party.
|
|
44
|
+
(c) Incorporate the Software or any portion thereof into any product or
|
|
45
|
+
service offered to third parties.
|
|
46
|
+
(d) Remove or alter any copyright, trademark, or proprietary notice
|
|
47
|
+
contained in the Software.
|
|
48
|
+
(e) Use the Software in any manner that competes with Licensor's business.
|
|
49
|
+
(f) Create derivative works based on the Software for distribution or
|
|
50
|
+
Commercial Use.
|
|
51
|
+
(g) Use the Software in any environment that processes sensitive personal
|
|
52
|
+
data without prior written authorization from Licensor.
|
|
53
|
+
|
|
54
|
+
4. Commercial License and Royalties
|
|
55
|
+
|
|
56
|
+
Any Commercial Use of the Software requires a separate commercial license
|
|
57
|
+
agreement signed by both parties. Such agreement shall specify:
|
|
58
|
+
|
|
59
|
+
(a) The scope of permitted Commercial Use.
|
|
60
|
+
(b) Royalty fees, which shall be calculated as a percentage of revenue or
|
|
61
|
+
a fixed fee per deployment, as agreed upon in writing.
|
|
62
|
+
(c) Duration and renewal terms.
|
|
63
|
+
|
|
64
|
+
Contact: resk-security@proton.me for commercial licensing inquiries.
|
|
65
|
+
|
|
66
|
+
5. Intellectual Property
|
|
67
|
+
|
|
68
|
+
All right, title, and interest in and to the Software, including all
|
|
69
|
+
intellectual property rights therein, remain the exclusive property of
|
|
70
|
+
Licensor. This License does not transfer any ownership rights to Licensee.
|
|
71
|
+
|
|
72
|
+
6. No Warranty
|
|
73
|
+
|
|
74
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
75
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
76
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
77
|
+
THE LICENSOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER
|
|
78
|
+
IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN
|
|
79
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
80
|
+
|
|
81
|
+
7. Limitation of Liability
|
|
82
|
+
|
|
83
|
+
In no event shall Licensor be liable for any indirect, incidental, special,
|
|
84
|
+
consequential, or punitive damages, including but not limited to loss of
|
|
85
|
+
profits, data, or use, arising out of or in connection with the Software,
|
|
86
|
+
whether based on warranty, contract, tort, or any other legal theory, even
|
|
87
|
+
if Licensor has been advised of the possibility of such damages.
|
|
88
|
+
|
|
89
|
+
8. Termination
|
|
90
|
+
|
|
91
|
+
This License terminates automatically if Licensee fails to comply with any
|
|
92
|
+
of its terms. Upon termination, Licensee must immediately cease all use of
|
|
93
|
+
the Software and destroy all copies. Licensor reserves the right to pursue
|
|
94
|
+
legal remedies for any unauthorized use.
|
|
95
|
+
|
|
96
|
+
9. Governing Law
|
|
97
|
+
|
|
98
|
+
This License shall be governed by and construed in accordance with the laws
|
|
99
|
+
of France, without regard to its conflict of laws principles.
|
|
100
|
+
|
|
101
|
+
10. Entire Agreement
|
|
102
|
+
|
|
103
|
+
This License constitutes the entire agreement between the parties concerning
|
|
104
|
+
the subject matter hereof and supersedes all prior agreements and
|
|
105
|
+
understandings.
|