tweek 0.3.0__py3-none-any.whl → 0.3.1__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.
tweek/__init__.py CHANGED
@@ -10,7 +10,7 @@ Tweek provides:
10
10
  - Per-skill/per-tool security policies
11
11
  """
12
12
 
13
- __version__ = "0.3.0"
13
+ __version__ = "0.3.1"
14
14
  __author__ = "Tommy Mancino"
15
15
 
16
16
  # "TOO MUCH PRESSURE!" - Tweek Tweak
tweek/audit.py CHANGED
@@ -2,7 +2,7 @@
2
2
  Tweek Skill Audit — Security analysis for skill files and tool descriptions.
3
3
 
4
4
  Reads skill content, detects language, translates non-English content,
5
- and runs the full 215-pattern regex analysis + LLM semantic review.
5
+ and runs the full 259-pattern regex analysis + LLM semantic review.
6
6
  Designed for one-time evaluation of skills before installation.
7
7
  """
8
8
 
@@ -173,7 +173,7 @@ def audit_content(
173
173
  except Exception:
174
174
  pass
175
175
 
176
- # Step 3: Pattern matching (all 215 patterns against English content)
176
+ # Step 3: Pattern matching (all 259 patterns against English content)
177
177
  try:
178
178
  from tweek.hooks.pre_tool_use import PatternMatcher
179
179
 
tweek/cli.py CHANGED
@@ -1181,26 +1181,27 @@ def _print_install_summary(
1181
1181
  @main.command(
1182
1182
  epilog="""\b
1183
1183
  Examples:
1184
- tweek unprotect Interactive — choose what to remove
1184
+ tweek unprotect Interactive — choose what to unprotect
1185
1185
  tweek unprotect claude-code Remove Claude Code hooks
1186
1186
  tweek unprotect claude-code --global Remove global Claude Code hooks
1187
1187
  tweek unprotect claude-desktop Remove from Claude Desktop
1188
- tweek unprotect --all Remove ALL Tweek data system-wide
1189
1188
  """
1190
1189
  )
1191
1190
  @click.argument("tool", required=False, type=click.Choice(
1192
1191
  ["claude-code", "openclaw", "claude-desktop", "chatgpt", "gemini"]))
1193
- @click.option("--all", "remove_all", is_flag=True, default=False,
1194
- help="Remove ALL Tweek data: hooks, skills, config, patterns, logs, MCP integrations")
1195
1192
  @click.option("--global", "unprotect_global", is_flag=True, default=False,
1196
1193
  help="Remove from ~/.claude/ (global installation)")
1197
1194
  @click.option("--confirm", is_flag=True, help="Skip confirmation prompt")
1198
- def unprotect(tool: str, remove_all: bool, unprotect_global: bool, confirm: bool):
1195
+ def unprotect(tool: str, unprotect_global: bool, confirm: bool):
1199
1196
  """Remove Tweek protection from an AI tool.
1200
1197
 
1198
+ This removes hooks and MCP configuration for a specific tool
1199
+ but keeps Tweek installed on your system. Use `tweek uninstall`
1200
+ to fully remove Tweek.
1201
+
1201
1202
  When run without arguments, launches an interactive wizard
1202
1203
  that walks through each protected tool asking if you want
1203
- to remove protection. Use --all to remove everything at once.
1204
+ to remove protection.
1204
1205
 
1205
1206
  This command can only be run from an interactive terminal.
1206
1207
  AI agents are blocked from running it.
@@ -1217,8 +1218,8 @@ def unprotect(tool: str, remove_all: bool, unprotect_global: bool, confirm: bool
1217
1218
  console.print("[white]Open a terminal and run the command directly.[/white]")
1218
1219
  raise SystemExit(1)
1219
1220
 
1220
- # No tool and no --all: run interactive wizard
1221
- if not tool and not remove_all:
1221
+ # No tool: run interactive wizard
1222
+ if not tool:
1222
1223
  _run_unprotect_wizard()
1223
1224
  return
1224
1225
 
@@ -1228,17 +1229,11 @@ def unprotect(tool: str, remove_all: bool, unprotect_global: bool, confirm: bool
1228
1229
  global_target = Path("~/.claude").expanduser()
1229
1230
  project_target = Path.cwd() / ".claude"
1230
1231
 
1231
- if remove_all:
1232
- _uninstall_everything(global_target, project_target, tweek_dir, confirm)
1233
- _show_package_removal_hint()
1234
- return
1235
-
1236
1232
  if tool == "claude-code":
1237
1233
  if unprotect_global:
1238
1234
  _uninstall_scope(global_target, tweek_dir, confirm, scope_label="global")
1239
1235
  else:
1240
1236
  _uninstall_scope(project_target, tweek_dir, confirm, scope_label="project")
1241
- _show_package_removal_hint()
1242
1237
  return
1243
1238
 
1244
1239
  if tool in ("claude-desktop", "chatgpt", "gemini"):
@@ -1266,6 +1261,57 @@ def unprotect(tool: str, remove_all: bool, unprotect_global: bool, confirm: bool
1266
1261
  console.print("[white]Manual step: remove tweek plugin from openclaw.json[/white]")
1267
1262
  return
1268
1263
 
1264
+
1265
+ @main.command(
1266
+ epilog="""\b
1267
+ Examples:
1268
+ tweek uninstall Interactive full removal
1269
+ tweek uninstall --all Remove ALL Tweek data system-wide
1270
+ tweek uninstall --all --confirm Remove everything without prompts
1271
+ """
1272
+ )
1273
+ @click.option("--all", "remove_all", is_flag=True, default=False,
1274
+ help="Remove ALL Tweek data: hooks, skills, config, patterns, logs, MCP integrations")
1275
+ @click.option("--confirm", is_flag=True, help="Skip confirmation prompts")
1276
+ def uninstall(remove_all: bool, confirm: bool):
1277
+ """Fully remove Tweek from your system.
1278
+
1279
+ Removes all hooks, skills, configuration, data, and optionally
1280
+ the Tweek package itself. For removing protection from a single
1281
+ tool without uninstalling, use `tweek unprotect` instead.
1282
+
1283
+ This command can only be run from an interactive terminal.
1284
+ AI agents are blocked from running it.
1285
+ """
1286
+ # ─────────────────────────────────────────────────────────────
1287
+ # HUMAN-ONLY GATE: Block non-interactive execution
1288
+ # ─────────────────────────────────────────────────────────────
1289
+ if not sys.stdin.isatty():
1290
+ console.print("[red]ERROR: tweek uninstall must be run from an interactive terminal.[/red]")
1291
+ console.print("[white]This command cannot be run by AI agents or automated scripts.[/white]")
1292
+ console.print("[white]Open a terminal and run the command directly.[/white]")
1293
+ raise SystemExit(1)
1294
+
1295
+ console.print(TWEEK_BANNER, style="cyan")
1296
+
1297
+ tweek_dir = Path("~/.tweek").expanduser()
1298
+ global_target = Path("~/.claude").expanduser()
1299
+ project_target = Path.cwd() / ".claude"
1300
+
1301
+ if not remove_all:
1302
+ # Interactive: ask what to remove
1303
+ console.print("[bold]What would you like to remove?[/bold]")
1304
+ console.print()
1305
+ console.print(" [bold]1.[/bold] Everything (all hooks, data, config, and package)")
1306
+ console.print(" [bold]2.[/bold] Cancel")
1307
+ console.print()
1308
+ choice = click.prompt("Select", type=click.IntRange(1, 2), default=2)
1309
+ if choice == 2:
1310
+ console.print("[white]Cancelled[/white]")
1311
+ return
1312
+ console.print()
1313
+
1314
+ _uninstall_everything(global_target, project_target, tweek_dir, confirm)
1269
1315
  _show_package_removal_hint()
1270
1316
 
1271
1317
 
@@ -1991,7 +2037,7 @@ def update(check: bool):
1991
2037
  Patterns are stored in ~/.tweek/patterns/ and can be updated
1992
2038
  independently of the Tweek application.
1993
2039
 
1994
- All 215 patterns are included free. PRO tier adds LLM review,
2040
+ All 259 patterns are included free. PRO tier adds LLM review,
1995
2041
  session analysis, and rate limiting.
1996
2042
  """
1997
2043
  import subprocess
@@ -2267,7 +2313,7 @@ def audit(path, translate, llm_review, json_out):
2267
2313
  credential theft, data exfiltration, and other attack patterns.
2268
2314
 
2269
2315
  Non-English content is detected and translated to English before
2270
- running all 215 regex patterns. LLM semantic review provides
2316
+ running all 259 regex patterns. LLM semantic review provides
2271
2317
  additional analysis for obfuscated attacks.
2272
2318
 
2273
2319
  \b
@@ -2695,7 +2741,7 @@ def protect_openclaw(port, paranoid, preset):
2695
2741
 
2696
2742
  # Show configuration
2697
2743
  console.print(f" Scanner: port {result.scanner_port} -> wrapping OpenClaw gateway")
2698
- console.print(f" Preset: {result.preset} (215 patterns + rate limiting)")
2744
+ console.print(f" Preset: {result.preset} (259 patterns + rate limiting)")
2699
2745
 
2700
2746
  # Check for API key
2701
2747
  anthropic_key = os.environ.get("ANTHROPIC_API_KEY")
tweek/config/__init__.py CHANGED
@@ -10,4 +10,12 @@ PATTERNS_FILE = CONFIG_DIR / "patterns.yaml"
10
10
  __all__ = [
11
11
  "ConfigManager", "SecurityTier", "ConfigIssue", "ConfigChange",
12
12
  "get_config", "CONFIG_DIR", "PATTERNS_FILE",
13
+ "TweekConfig", "PatternsConfig",
13
14
  ]
15
+
16
+ # Lazy imports for Pydantic models to avoid import cost when not needed
17
+ def __getattr__(name):
18
+ if name in ("TweekConfig", "PatternsConfig"):
19
+ from tweek.config.models import TweekConfig, PatternsConfig
20
+ return {"TweekConfig": TweekConfig, "PatternsConfig": PatternsConfig}[name]
21
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
tweek/config/manager.py CHANGED
@@ -685,8 +685,40 @@ class ConfigManager:
685
685
  suggestion=suggestion,
686
686
  ))
687
687
 
688
+ # Run Pydantic structural validation on merged config
689
+ try:
690
+ merged = self._get_merged()
691
+ pydantic_issues = self._validate_with_pydantic(merged)
692
+ # Deduplicate: only add Pydantic issues not already caught above
693
+ existing_messages = {i.message for i in issues}
694
+ for pi in pydantic_issues:
695
+ if pi.message not in existing_messages:
696
+ issues.append(pi)
697
+ except Exception:
698
+ pass # Pydantic validation is additive, never blocks
699
+
688
700
  return issues
689
701
 
702
+ def _validate_with_pydantic(self, config: Dict) -> List[ConfigIssue]:
703
+ """Run Pydantic model validation on merged config."""
704
+ from pydantic import ValidationError
705
+ from tweek.config.models import TweekConfig
706
+
707
+ try:
708
+ TweekConfig.model_validate(config)
709
+ return []
710
+ except ValidationError as e:
711
+ issues = []
712
+ for err in e.errors():
713
+ loc = ".".join(str(p) for p in err["loc"])
714
+ issues.append(ConfigIssue(
715
+ level="error",
716
+ key=loc,
717
+ message=err["msg"],
718
+ suggestion="",
719
+ ))
720
+ return issues
721
+
690
722
  def diff_preset(self, preset_name: str) -> List[ConfigChange]:
691
723
  """
692
724
  Show what would change if a preset were applied.
tweek/config/models.py ADDED
@@ -0,0 +1,307 @@
1
+ """
2
+ Pydantic models for Tweek configuration validation.
3
+
4
+ These models define the schema for tiers.yaml (the main configuration file)
5
+ and patterns.yaml (attack pattern definitions). They provide:
6
+ - Type-safe configuration loading with automatic validation
7
+ - Human-readable error messages for invalid configuration
8
+ - JSON Schema export for documentation and IDE support
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from enum import Enum
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ from pydantic import BaseModel, Field, field_validator, model_validator
18
+
19
+
20
+ # ============================================================================
21
+ # Enums
22
+ # ============================================================================
23
+
24
+
25
+ class SecurityTierValue(str, Enum):
26
+ """Valid security tier values."""
27
+ SAFE = "safe"
28
+ DEFAULT = "default"
29
+ RISKY = "risky"
30
+ DANGEROUS = "dangerous"
31
+
32
+
33
+ class NonEnglishHandling(str, Enum):
34
+ """How non-English content is handled during screening."""
35
+ ESCALATE = "escalate"
36
+ TRANSLATE = "translate"
37
+ BOTH = "both"
38
+ NONE = "none"
39
+
40
+
41
+ class PatternSeverity(str, Enum):
42
+ """Severity levels for attack patterns."""
43
+ CRITICAL = "critical"
44
+ HIGH = "high"
45
+ MEDIUM = "medium"
46
+ LOW = "low"
47
+
48
+
49
+ class PatternConfidence(str, Enum):
50
+ """Confidence levels for attack pattern matches."""
51
+ DETERMINISTIC = "deterministic"
52
+ HEURISTIC = "heuristic"
53
+ CONTEXTUAL = "contextual"
54
+
55
+
56
+ # ============================================================================
57
+ # Configuration Section Models
58
+ # ============================================================================
59
+
60
+
61
+ class LLMReviewLocalConfig(BaseModel):
62
+ """Configuration for local LLM server (Ollama, LM Studio)."""
63
+ enabled: bool = True
64
+ probe_timeout: float = Field(default=2.0, gt=0)
65
+ timeout_seconds: float = Field(default=30.0, gt=0)
66
+ ollama_host: Optional[str] = None
67
+ lm_studio_host: Optional[str] = None
68
+ preferred_models: List[str] = Field(default_factory=list)
69
+ validate_on_first_use: bool = True
70
+ min_validation_score: float = Field(default=0.6, ge=0.0, le=1.0)
71
+
72
+ model_config = {"extra": "allow"}
73
+
74
+
75
+ class LLMReviewFallbackConfig(BaseModel):
76
+ """Configuration for LLM fallback chain."""
77
+ enabled: bool = True
78
+ order: List[str] = Field(default_factory=lambda: ["local", "anthropic", "openai"])
79
+
80
+ model_config = {"extra": "allow"}
81
+
82
+
83
+ class LLMReviewConfig(BaseModel):
84
+ """Configuration for LLM-based semantic review."""
85
+ enabled: bool = True
86
+ provider: str = "auto"
87
+ model: str = "auto"
88
+ base_url: Optional[str] = None
89
+ api_key_env: Optional[str] = None
90
+ timeout_seconds: float = Field(default=15.0, gt=0)
91
+ local: LLMReviewLocalConfig = Field(default_factory=LLMReviewLocalConfig)
92
+ fallback: LLMReviewFallbackConfig = Field(default_factory=LLMReviewFallbackConfig)
93
+
94
+ model_config = {"extra": "allow"}
95
+
96
+
97
+ class RateLimitingConfig(BaseModel):
98
+ """Configuration for request rate limiting."""
99
+ enabled: bool = True
100
+ burst_window_seconds: int = Field(default=10, gt=0)
101
+ burst_threshold: int = Field(default=5, gt=0)
102
+ max_per_minute: int = Field(default=60, gt=0)
103
+ max_dangerous_per_minute: int = Field(default=10, gt=0)
104
+ max_same_command_per_minute: int = Field(default=5, gt=0)
105
+
106
+ model_config = {"extra": "allow"}
107
+
108
+
109
+ class SessionAnalysisConfig(BaseModel):
110
+ """Configuration for session-level analysis."""
111
+ enabled: bool = True
112
+ lookback_minutes: int = Field(default=30, gt=0)
113
+ alert_on_risk_score: float = Field(default=0.7, ge=0.0, le=1.0)
114
+
115
+ model_config = {"extra": "allow"}
116
+
117
+
118
+ class HeuristicScorerConfig(BaseModel):
119
+ """Configuration for heuristic scoring bridge."""
120
+ enabled: bool = True
121
+ threshold: float = Field(default=0.4, ge=0.0, le=1.0)
122
+ log_all_scores: bool = False
123
+
124
+ model_config = {"extra": "allow"}
125
+
126
+
127
+ class LocalModelConfig(BaseModel):
128
+ """Configuration for local ONNX model inference."""
129
+ enabled: bool = True
130
+ model: str = "auto"
131
+ escalate_to_llm: bool = True
132
+ escalate_min_confidence: float = Field(default=0.1, ge=0.0, le=1.0)
133
+ escalate_max_confidence: float = Field(default=0.9, ge=0.0, le=1.0)
134
+
135
+ @model_validator(mode="after")
136
+ def check_escalation_bounds(self) -> "LocalModelConfig":
137
+ if self.escalate_min_confidence >= self.escalate_max_confidence:
138
+ raise ValueError(
139
+ f"escalate_min_confidence ({self.escalate_min_confidence}) "
140
+ f"must be less than escalate_max_confidence ({self.escalate_max_confidence})"
141
+ )
142
+ return self
143
+
144
+ model_config = {"extra": "allow"}
145
+
146
+
147
+ class TierDefinition(BaseModel):
148
+ """Definition of a security tier."""
149
+ description: str
150
+ screening: List[str] = Field(default_factory=list)
151
+
152
+
153
+ class EscalationRule(BaseModel):
154
+ """A content-based escalation rule."""
155
+ pattern: str
156
+ description: str
157
+ escalate_to: SecurityTierValue
158
+
159
+ @field_validator("pattern")
160
+ @classmethod
161
+ def validate_regex(cls, v: str) -> str:
162
+ try:
163
+ re.compile(v)
164
+ except re.error as e:
165
+ raise ValueError(f"Invalid regex pattern: {e}") from e
166
+ return v
167
+
168
+
169
+ class SensitiveDirectory(BaseModel):
170
+ """A sensitive directory that triggers path boundary escalation."""
171
+ pattern: str
172
+ escalate_to: SecurityTierValue
173
+ description: str = ""
174
+
175
+
176
+ class PathBoundaryConfig(BaseModel):
177
+ """Configuration for path boundary escalation."""
178
+ enabled: bool = True
179
+ default_escalate_to: SecurityTierValue = SecurityTierValue.RISKY
180
+ sensitive_directories: List[SensitiveDirectory] = Field(default_factory=list)
181
+
182
+
183
+ class OpenClawConfig(BaseModel):
184
+ """Configuration for OpenClaw integration."""
185
+ enabled: bool = False
186
+ gateway_port: int = Field(default=18789, gt=0, le=65535)
187
+ scanner_port: int = Field(default=9878, gt=0, le=65535)
188
+ plugin_installed: bool = False
189
+ preset: str = "cautious"
190
+
191
+ model_config = {"extra": "allow"}
192
+
193
+
194
+ # ============================================================================
195
+ # Root Configuration Model
196
+ # ============================================================================
197
+
198
+
199
+ class TweekConfig(BaseModel):
200
+ """
201
+ Root Pydantic model for Tweek configuration (tiers.yaml / config.yaml).
202
+
203
+ Validates the merged configuration from builtin, user, and project layers.
204
+ Uses extra="allow" at the root level to be forward-compatible with new
205
+ config keys added in future versions.
206
+ """
207
+ version: Optional[int] = None
208
+
209
+ # Core tool/skill classification
210
+ tools: Dict[str, SecurityTierValue] = Field(default_factory=dict)
211
+ skills: Dict[str, SecurityTierValue] = Field(default_factory=dict)
212
+ default_tier: SecurityTierValue = SecurityTierValue.DEFAULT
213
+
214
+ # Tier definitions
215
+ tiers: Dict[str, TierDefinition] = Field(default_factory=dict)
216
+
217
+ # Screening configuration
218
+ llm_review: Optional[LLMReviewConfig] = None
219
+ rate_limiting: Optional[RateLimitingConfig] = None
220
+ session_analysis: Optional[SessionAnalysisConfig] = None
221
+ heuristic_scorer: Optional[HeuristicScorerConfig] = None
222
+ local_model: Optional[LocalModelConfig] = None
223
+
224
+ # Escalation rules
225
+ escalations: List[EscalationRule] = Field(default_factory=list)
226
+
227
+ # Path boundary
228
+ path_boundary: Optional[PathBoundaryConfig] = None
229
+
230
+ # Non-English handling
231
+ non_english_handling: NonEnglishHandling = NonEnglishHandling.ESCALATE
232
+
233
+ # Integration configs
234
+ proxy: Optional[Dict[str, Any]] = None
235
+ mcp: Optional[Dict[str, Any]] = None
236
+ sandbox: Optional[Dict[str, Any]] = None
237
+ isolation_chamber: Optional[Dict[str, Any]] = None
238
+ plugins: Optional[Dict[str, Any]] = None
239
+ openclaw: Optional[OpenClawConfig] = None
240
+
241
+ model_config = {"extra": "allow"}
242
+
243
+ @field_validator("tools", mode="before")
244
+ @classmethod
245
+ def coerce_tool_tiers(cls, v: Any) -> Any:
246
+ """Accept string tier values and coerce to SecurityTierValue."""
247
+ if isinstance(v, dict):
248
+ return {k: SecurityTierValue(val) if isinstance(val, str) else val for k, val in v.items()}
249
+ return v
250
+
251
+ @field_validator("skills", mode="before")
252
+ @classmethod
253
+ def coerce_skill_tiers(cls, v: Any) -> Any:
254
+ """Accept string tier values and coerce to SecurityTierValue."""
255
+ if isinstance(v, dict):
256
+ return {k: SecurityTierValue(val) if isinstance(val, str) else val for k, val in v.items()}
257
+ return v
258
+
259
+
260
+ # ============================================================================
261
+ # Pattern Schema Model
262
+ # ============================================================================
263
+
264
+
265
+ class PatternDefinition(BaseModel):
266
+ """A single attack pattern definition from patterns.yaml."""
267
+ id: int
268
+ name: str
269
+ description: str
270
+ regex: str
271
+ severity: PatternSeverity
272
+ confidence: PatternConfidence
273
+ family: Optional[str] = None
274
+
275
+ @field_validator("regex")
276
+ @classmethod
277
+ def validate_regex(cls, v: str) -> str:
278
+ try:
279
+ re.compile(v)
280
+ except re.error as e:
281
+ raise ValueError(f"Invalid regex in pattern: {e}") from e
282
+ return v
283
+
284
+
285
+ class PatternsConfig(BaseModel):
286
+ """Root model for patterns.yaml."""
287
+ version: int
288
+ pattern_count: int = 0
289
+ patterns: List[PatternDefinition] = Field(default_factory=list)
290
+
291
+ @model_validator(mode="after")
292
+ def check_pattern_count(self) -> "PatternsConfig":
293
+ actual = len(self.patterns)
294
+ if self.pattern_count > 0 and actual != self.pattern_count:
295
+ raise ValueError(
296
+ f"pattern_count ({self.pattern_count}) does not match "
297
+ f"actual number of patterns ({actual})"
298
+ )
299
+ return self
300
+
301
+ @model_validator(mode="after")
302
+ def check_unique_ids(self) -> "PatternsConfig":
303
+ ids = [p.id for p in self.patterns]
304
+ if len(ids) != len(set(ids)):
305
+ dupes = [i for i in ids if ids.count(i) > 1]
306
+ raise ValueError(f"Duplicate pattern IDs: {set(dupes)}")
307
+ return self
@@ -1,5 +1,5 @@
1
1
  # Tweek Attack Pattern Definitions v3
2
- # All 215 patterns included FREE
2
+ # All 259 patterns included FREE
3
3
  #
4
4
  # Update via: tweek update (pulls from github.com/gettweek/tweek)
5
5
  #
@@ -11,7 +11,7 @@ web pages, documents, and other ingested content.
11
11
 
12
12
  Screening Pipeline:
13
13
  1. Language Detection — identify non-English content
14
- 2. Pattern Matching — 215 regex patterns for known attack vectors
14
+ 2. Pattern Matching — 259 regex patterns for known attack vectors
15
15
  3. LLM Review — semantic analysis if non-English escalation triggers
16
16
 
17
17
  Claude Code PostToolUse Protocol:
tweek/licensing.py CHANGED
@@ -74,7 +74,7 @@ class LicenseInfo:
74
74
  # Only compliance and team management features require a license.
75
75
  TIER_FEATURES = {
76
76
  Tier.FREE: [
77
- "pattern_matching", # All 215 patterns included free
77
+ "pattern_matching", # All 259 patterns included free
78
78
  "basic_logging",
79
79
  "vault_storage",
80
80
  "cli_commands",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tweek
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Defense-in-depth security for AI coding assistants - protect credentials, code, and system from prompt injection attacks
5
5
  Author: Tommy Mancino
6
6
  License-Expression: Apache-2.0
@@ -45,6 +45,7 @@ Provides-Extra: dev
45
45
  Requires-Dist: pytest>=7.0; extra == "dev"
46
46
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
47
47
  Requires-Dist: pytest-xdist>=3.5.0; extra == "dev"
48
+ Requires-Dist: hypothesis>=6.98.0; extra == "dev"
48
49
  Requires-Dist: black>=23.0; extra == "dev"
49
50
  Requires-Dist: ruff>=0.1.0; extra == "dev"
50
51
  Requires-Dist: twine>=4.0; extra == "dev"
@@ -165,7 +166,7 @@ Turn 3: cat ~/.ssh/id_rsa → BLOCKED: path_escalation anomaly
165
166
 
166
167
  **Response injection** — Malicious instructions hidden in tool responses are caught at ingestion.
167
168
 
168
- See the full [Attack Patterns Reference](docs/ATTACK_PATTERNS.md) for all 259 patterns across 22 categories.
169
+ See the full [Attack Patterns Reference](docs/ATTACK_PATTERNS.md) for all 259 patterns across 11 categories.
169
170
 
170
171
  ---
171
172
 
@@ -1,22 +1,23 @@
1
- tweek/__init__.py,sha256=v1mBM39Sp-MgonHi8fotmfcN7dxbow_Eh9xdbSYnjN4,360
1
+ tweek/__init__.py,sha256=NppVIy7NWIJrgFbWfAVveJtg-UdwqA9ux98hciWBeLM,360
2
2
  tweek/_keygen.py,sha256=UapwIKNSwaRWdqHoJoF3hmKuiux6aIiFGe8WVskTbI8,1286
3
- tweek/audit.py,sha256=Bp4RETwdiHpT2EEi45atZa0LlJUOtALhrc3UT8MHvF8,8868
4
- tweek/cli.py,sha256=0AxYSIQ56Y2OHNVGSocNksYkiiLfUFNx5XvBUzCtZZI,254670
3
+ tweek/audit.py,sha256=OmCUagbx_fkCorcrZt2ebTtDm-rr4fRKkZpxZdvZens,8868
4
+ tweek/cli.py,sha256=Ad54k7bDws9Eg7z3oeEVR85Ni9DQz72JJVUFC1WV_zA,256803
5
5
  tweek/cli_helpers.py,sha256=Q2NTOkyRTOIPNLMqY2jA5_tuzDPksAGwGXYPRK3bzoY,5538
6
6
  tweek/cli_model.py,sha256=iMZStFqA0Nqyzm4rxSbhD4v-AqcO6h5NI72AR7cldoY,12853
7
7
  tweek/diagnostics.py,sha256=KbtXQH8QrRBoyIFWumL6q9--aQQdR0tUo2GzjMhwpII,24601
8
- tweek/licensing.py,sha256=4Pt34t8Y60jaLMBYLjnmLs_0o_LUahOhGflhXeZtuPU,11703
9
- tweek/config/__init__.py,sha256=C_kQm0LqYdM67E9wNi6bsX2V7xz7GY4HiICb_XlrX8A,362
8
+ tweek/licensing.py,sha256=wYN8wBYVCp1RbAi_sWeF7gKKBSU116ncX3tnZawYUpQ,11703
9
+ tweek/config/__init__.py,sha256=ENwimeLZd2gSJXpkASMY45hbMUDn2RwM-Zl_RMvpCbQ,772
10
10
  tweek/config/allowed_dirs.yaml,sha256=dMF_DqKgQThzkdIEoXzDBfAjbopGrk0HTkiM7ENmBaU,788
11
11
  tweek/config/families.yaml,sha256=jkNO0UsmX3MFlTKC9Or3p8_MlD3ZtHM0SrQIYFqx9i8,18212
12
- tweek/config/manager.py,sha256=FTBJ4sMwnENH9tfvKB5d1SFve1R__k5j2KXb38vn1CA,39347
13
- tweek/config/patterns.yaml,sha256=8ow--0qdPJNjIY94j-vDEcrHt-TYgf9uuPCiqSMCIEQ,85376
12
+ tweek/config/manager.py,sha256=Jk9l_UJM9e5_fxTvWFXrU0677u9HCttmunahp36woBE,40591
13
+ tweek/config/models.py,sha256=RbVjC2pxnkrBKanS6QGDrHwPVkmss5ouG_dqAHf_C3Q,10018
14
+ tweek/config/patterns.yaml,sha256=hu0lphSN0i_bY8kla65bTaBEQR8phhrb3BLC1KprMLw,85376
14
15
  tweek/config/tiers.yaml,sha256=9hIXQ9izVKXd8ptoCsQiBo2r_XY8RvIk7VWrhWggkbc,10191
15
16
  tweek/hooks/__init__.py,sha256=GcgDjPdhZayxmyZ4-GfBa-ISARNtt9087RsuprRq2-s,54
16
17
  tweek/hooks/break_glass.py,sha256=GNMhCtLWPylNMlQ5QfsoUkEjgIT1Uk1Ik7HvRWeE5N8,4636
17
18
  tweek/hooks/feedback.py,sha256=uuA4opHYyBHC5sElBz-fr2Je3cg2DAv-aRHvETZcag0,6555
18
19
  tweek/hooks/overrides.py,sha256=1Yw_NPpZMvcFG_uyNY-ouBKSSomnxOptRedSjzkkhmE,18635
19
- tweek/hooks/post_tool_use.py,sha256=DiAnWOBd9t4vpMz1JsgUjYzToU6i-igesP2Vk83AAAc,17195
20
+ tweek/hooks/post_tool_use.py,sha256=22ugZdlZn2Q0eUcUucelrF18N7mCgaC_agb7kZT51Ww,17195
20
21
  tweek/hooks/pre_tool_use.py,sha256=70XbonRSGh8rYpDlI4R_Z5Ug2LwU4iLyLsS87I5xlqc,71743
21
22
  tweek/integrations/__init__.py,sha256=sl7wFwbygmnruugX4bO2EUjoXxBlCpzTKbj-1zHuUPg,78
22
23
  tweek/integrations/openclaw.py,sha256=jX99__ODGI7Cq6gclSTK2pI5lsI7UGh5_iCHmq1R8RY,13798
@@ -113,11 +114,11 @@ tweek/skills/scanner.py,sha256=PaeZNnwxLTGls2O3hQaDgBhGw9jVJThPjfKCY_05_nI,27574
113
114
  tweek/vault/__init__.py,sha256=L408fjdRYL8-VqLEsyyHSO9PkBDhd_2mPIbrCu53YhM,980
114
115
  tweek/vault/cross_platform.py,sha256=D4UvX_7OpSo8iRx5sc2OUUWQIk8JHhgeFBYk1MbyIj4,8251
115
116
  tweek/vault/keychain.py,sha256=XL18-SUj7HwuqxLXZDViuCH81--KMu68jN9Szn1aeyw,10624
116
- tweek-0.3.0.dist-info/licenses/LICENSE,sha256=rjoDzr1vAf0bsqZglpIyekU5aewIkCk4jHZZDvVI2BE,15269
117
- tweek-0.3.0.dist-info/licenses/NOTICE,sha256=taQokyDes5UTRNEC67G-13VmqvUyTOncrrT33pCcWL0,8729
117
+ tweek-0.3.1.dist-info/licenses/LICENSE,sha256=rjoDzr1vAf0bsqZglpIyekU5aewIkCk4jHZZDvVI2BE,15269
118
+ tweek-0.3.1.dist-info/licenses/NOTICE,sha256=taQokyDes5UTRNEC67G-13VmqvUyTOncrrT33pCcWL0,8729
118
119
  tweek-openclaw-plugin/node_modules/flatted/python/flatted.py,sha256=UYburBDqkySaTfSpntPCUJRxiBGcplusJM7ECX8FEgA,3860
119
- tweek-0.3.0.dist-info/METADATA,sha256=4eLu77u_VjfeqHnZ_-sIXRthm3In7dOHRnGkAaqQy2Y,11889
120
- tweek-0.3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
121
- tweek-0.3.0.dist-info/entry_points.txt,sha256=YXThD6UiF5XQXwqW33sphsvz-Bl4Zm6pm-xq-5wcCYE,1337
122
- tweek-0.3.0.dist-info/top_level.txt,sha256=jtNcCxjoGXN8IBqEVL0F3LHDrZD_B0S-4XF9-Ur7Pbc,28
123
- tweek-0.3.0.dist-info/RECORD,,
120
+ tweek-0.3.1.dist-info/METADATA,sha256=iR7qpsuY7fLnF2DO8OWFrqUTE2vuDv3_VNMWddDIZMU,11939
121
+ tweek-0.3.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
122
+ tweek-0.3.1.dist-info/entry_points.txt,sha256=YXThD6UiF5XQXwqW33sphsvz-Bl4Zm6pm-xq-5wcCYE,1337
123
+ tweek-0.3.1.dist-info/top_level.txt,sha256=jtNcCxjoGXN8IBqEVL0F3LHDrZD_B0S-4XF9-Ur7Pbc,28
124
+ tweek-0.3.1.dist-info/RECORD,,
File without changes