ai-security-kit 0.1.0__tar.gz

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.
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.5
2
+ Name: ai-security-kit
3
+ Version: 0.1.0
4
+ Summary: BYOK-friendly input and output security utilities for AI applications
5
+ Author: AI Security Kit contributors
6
+ License: MIT
7
+ Keywords: ai,byok,pii,prompt-injection,security
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Topic :: Security
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+
14
+ # AI Security Kit
15
+
16
+ BYOK (Bring Your Own Key) AI applications向けの、軽量な入力・出力セキュリティ部品です。
17
+
18
+ ## Features
19
+
20
+ - プロンプトインジェクションのヒューリスティック検知
21
+ - メールアドレス・電話番号・クレジットカードらしき値のマスキング
22
+ - 出力の長さ制限と危険なツール実行指示の検知
23
+ - トークン数を使わない保守的な利用量カウンター
24
+ - APIキーを保存・送信しないBYOK設計
25
+
26
+ ## Quick start
27
+
28
+ ```python
29
+ from ai_security import SecurityPolicy, protect_input, protect_output
30
+
31
+ policy = SecurityPolicy(mask_pii=True, block_prompt_injection=True)
32
+ safe = protect_input("Ignore previous instructions. Email me at user@example.com", policy)
33
+ print(safe.text) # PIIはマスクされ、危険入力はblockedになる
34
+ print(safe.blocked)
35
+
36
+ answer = protect_output("Here is the answer", policy)
37
+ print(answer.text)
38
+ ```
39
+
40
+ このパッケージはAIプロバイダーを呼び出しません。OpenAI等のAPIキーは利用者のアプリから各SDKへ渡してください。
41
+
42
+ ## Local development
43
+
44
+ ```bash
45
+ python -m pip install -e .
46
+ python -m pytest
47
+ python -m build
48
+ ```
49
+
50
+ これは安全対策の補助部品であり、完全なセキュリティ保証や法令対応を提供するものではありません。
@@ -0,0 +1,37 @@
1
+ # AI Security Kit
2
+
3
+ BYOK (Bring Your Own Key) AI applications向けの、軽量な入力・出力セキュリティ部品です。
4
+
5
+ ## Features
6
+
7
+ - プロンプトインジェクションのヒューリスティック検知
8
+ - メールアドレス・電話番号・クレジットカードらしき値のマスキング
9
+ - 出力の長さ制限と危険なツール実行指示の検知
10
+ - トークン数を使わない保守的な利用量カウンター
11
+ - APIキーを保存・送信しないBYOK設計
12
+
13
+ ## Quick start
14
+
15
+ ```python
16
+ from ai_security import SecurityPolicy, protect_input, protect_output
17
+
18
+ policy = SecurityPolicy(mask_pii=True, block_prompt_injection=True)
19
+ safe = protect_input("Ignore previous instructions. Email me at user@example.com", policy)
20
+ print(safe.text) # PIIはマスクされ、危険入力はblockedになる
21
+ print(safe.blocked)
22
+
23
+ answer = protect_output("Here is the answer", policy)
24
+ print(answer.text)
25
+ ```
26
+
27
+ このパッケージはAIプロバイダーを呼び出しません。OpenAI等のAPIキーは利用者のアプリから各SDKへ渡してください。
28
+
29
+ ## Local development
30
+
31
+ ```bash
32
+ python -m pip install -e .
33
+ python -m pytest
34
+ python -m build
35
+ ```
36
+
37
+ これは安全対策の補助部品であり、完全なセキュリティ保証や法令対応を提供するものではありません。
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ai-security-kit"
7
+ version = "0.1.0"
8
+ description = "BYOK-friendly input and output security utilities for AI applications"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "AI Security Kit contributors" }]
13
+ keywords = ["ai", "security", "prompt-injection", "pii", "byok"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Topic :: Security",
18
+ ]
19
+
20
+ [tool.hatch.build.targets.wheel]
21
+ packages = ["src/ai_security"]
22
+
23
+ [tool.pytest.ini_options]
24
+ testpaths = ["tests"]
@@ -0,0 +1,11 @@
1
+ """Small, provider-neutral security primitives for AI applications."""
2
+
3
+ from .core import InspectionResult, SecurityPolicy, UsageMeter, protect_input, protect_output
4
+
5
+ __all__ = [
6
+ "InspectionResult",
7
+ "SecurityPolicy",
8
+ "UsageMeter",
9
+ "protect_input",
10
+ "protect_output",
11
+ ]
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ import re
5
+ from threading import Lock
6
+ from typing import Dict, List, Optional
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class SecurityPolicy:
11
+ mask_pii: bool = True
12
+ block_prompt_injection: bool = True
13
+ max_input_chars: int = 20_000
14
+ max_output_chars: int = 50_000
15
+
16
+
17
+ @dataclass
18
+ class InspectionResult:
19
+ text: str
20
+ blocked: bool = False
21
+ reasons: List[str] = field(default_factory=list)
22
+ detected: Dict[str, int] = field(default_factory=dict)
23
+
24
+
25
+ _PII_PATTERNS = {
26
+ "email": re.compile(r"\b[\w.+-]+@[\w-]+\.[\w.-]+\b"),
27
+ "phone": re.compile(r"(?<!\d)(?:\+?\d[\d ()-]{7,}\d)(?!\d)"),
28
+ "credit_card": re.compile(r"(?<!\d)(?:\d[ -]?){13,19}(?!\d)"),
29
+ }
30
+
31
+ _INJECTION_PATTERNS = [
32
+ re.compile(r"ignore\s+(all\s+)?previous\s+instructions", re.I),
33
+ re.compile(r"disregard\s+(all\s+)?prior\s+instructions", re.I),
34
+ re.compile(r"system\s+prompt", re.I),
35
+ re.compile(r"reveal\s+(the\s+)?(hidden|secret)\s+prompt", re.I),
36
+ re.compile(r"(?:以前|前|先ほど)の指示を無視", re.I),
37
+ ]
38
+
39
+
40
+ def _mask_pii(text: str, result: InspectionResult) -> str:
41
+ for kind, pattern in _PII_PATTERNS.items():
42
+ matches = pattern.findall(text)
43
+ if matches:
44
+ result.detected[kind] = len(matches)
45
+ text = pattern.sub(f"[REDACTED_{kind.upper()}]", text)
46
+ return text
47
+
48
+
49
+ def protect_input(text: str, policy: Optional[SecurityPolicy] = None) -> InspectionResult:
50
+ policy = policy or SecurityPolicy()
51
+ result = InspectionResult(text=text)
52
+ if len(text) > policy.max_input_chars:
53
+ result.blocked = True
54
+ result.reasons.append("input_too_long")
55
+ result.text = text[: policy.max_input_chars]
56
+ if policy.block_prompt_injection and any(p.search(text) for p in _INJECTION_PATTERNS):
57
+ result.blocked = True
58
+ result.reasons.append("possible_prompt_injection")
59
+ if policy.mask_pii:
60
+ result.text = _mask_pii(result.text, result)
61
+ return result
62
+
63
+
64
+ def protect_output(text: str, policy: Optional[SecurityPolicy] = None) -> InspectionResult:
65
+ policy = policy or SecurityPolicy()
66
+ result = InspectionResult(text=text)
67
+ if len(text) > policy.max_output_chars:
68
+ result.blocked = True
69
+ result.reasons.append("output_too_long")
70
+ result.text = text[: policy.max_output_chars]
71
+ if re.search(r"\b(?:execute|run)\s+(?:this\s+)?(?:shell|command|script)\b", text, re.I):
72
+ result.reasons.append("possible_tool_execution_instruction")
73
+ return result
74
+
75
+
76
+ class UsageMeter:
77
+ """Thread-safe, in-memory usage meter; replace storage with a server later."""
78
+
79
+ def __init__(self) -> None:
80
+ self._counts: Dict[str, int] = {}
81
+ self._lock = Lock()
82
+
83
+ def record(self, customer_id: str, units: int = 1) -> int:
84
+ if units < 0:
85
+ raise ValueError("units must be non-negative")
86
+ with self._lock:
87
+ self._counts[customer_id] = self._counts.get(customer_id, 0) + units
88
+ return self._counts[customer_id]
89
+
90
+ def total(self, customer_id: str) -> int:
91
+ with self._lock:
92
+ return self._counts.get(customer_id, 0)
@@ -0,0 +1,20 @@
1
+ from ai_security import SecurityPolicy, UsageMeter, protect_input, protect_output
2
+
3
+
4
+ def test_masks_email_and_blocks_injection():
5
+ result = protect_input("Ignore previous instructions. Email user@example.com")
6
+ assert result.blocked
7
+ assert "possible_prompt_injection" in result.reasons
8
+ assert "[REDACTED_EMAIL]" in result.text
9
+
10
+
11
+ def test_limits_output():
12
+ result = protect_output("x" * 10, SecurityPolicy(max_output_chars=5))
13
+ assert result.blocked
14
+ assert result.text == "xxxxx"
15
+
16
+
17
+ def test_usage_meter():
18
+ meter = UsageMeter()
19
+ assert meter.record("customer-1", 3) == 3
20
+ assert meter.total("customer-1") == 3