cipher-security 0.2.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.
bot/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # Bot package β€” Signal bot utilities.
bot/bot.py ADDED
@@ -0,0 +1,234 @@
1
+ # Copyright (c) 2026 defconxt. All rights reserved.
2
+ # Licensed under AGPL-3.0 β€” see LICENSE file for details.
3
+ """CIPHER Signal bot β€” CipherCommand handler and watchdog main loop.
4
+
5
+ Entry point for the Signal bot container. Registers CipherCommand with
6
+ signalbot, filters by whitelist, maps prefixes, dispatches to Gateway via
7
+ executor, strips markdown, and manages session history.
8
+
9
+ Watchdog: outer while-True retries bot.start() on any exception with
10
+ exponential backoff (1s base, 2x multiplier, 60s max).
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import asyncio
15
+ import logging
16
+ import time
17
+ from typing import Any
18
+
19
+ from signalbot import Command # type: ignore[import]
20
+
21
+ from bot.format import map_prefix, strip_markdown
22
+ from bot.session import SessionManager
23
+ from gateway.gateway import Gateway
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # Disambiguation keyword map
28
+ _CLARIFY_MAP: dict[str, str] = {
29
+ "offensive": "RED",
30
+ "defensive": "BLUE",
31
+ }
32
+
33
+
34
+ class CipherCommand(Command):
35
+ """signalbot Command handler for CIPHER.
36
+
37
+ Filters senders against a whitelist, maps short prefixes, dispatches to
38
+ Gateway via asyncio executor (non-blocking), strips markdown, manages
39
+ session history, and handles disambiguation flow.
40
+
41
+ Args:
42
+ gateway: Constructed Gateway instance.
43
+ session: SessionManager for conversation history.
44
+ whitelist: Set of E.164 phone numbers allowed to use the bot.
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ gateway: Gateway,
50
+ session: SessionManager,
51
+ whitelist: set[str],
52
+ ) -> None:
53
+ super().__init__()
54
+ self._gateway = gateway
55
+ self._session = session
56
+ self._whitelist = whitelist
57
+ # Keyed by sender E.164 number; value is the original query awaiting
58
+ # clarification (offensive vs defensive).
59
+ self._clarification: dict[str, str] = {}
60
+
61
+ async def handle(self, c: Any) -> None:
62
+ """Handle an incoming Signal message.
63
+
64
+ Args:
65
+ c: signalbot Context (or MockContext in tests).
66
+ """
67
+ text: str = (c.message.text or "").strip()
68
+
69
+ # --- Skip empty messages (typing indicators, system events) ---
70
+ if not text:
71
+ return
72
+
73
+ # --- Whitelist gate ---
74
+ # Signal may identify senders by UUID or phone number depending on
75
+ # privacy settings. Check all available identifiers.
76
+ sender_ids = {c.message.source, c.message.source_uuid}
77
+ if c.message.source_number:
78
+ sender_ids.add(c.message.source_number)
79
+ if not sender_ids & self._whitelist:
80
+ return
81
+ sender: str = c.message.source_number or c.message.source
82
+
83
+ # --- /reset command ---
84
+ if text.strip() == "/reset":
85
+ self._session.reset(sender)
86
+ await c.send("Session cleared.")
87
+ return
88
+
89
+ # --- Disambiguation reply check ---
90
+ if sender in self._clarification:
91
+ await self._handle_clarification(c, sender, text)
92
+ return
93
+
94
+ # --- Acknowledge with brain emoji ---
95
+ await c.react("🧠")
96
+
97
+ # --- Map short prefix ---
98
+ mapped = map_prefix(text)
99
+
100
+ # --- Fetch session history ---
101
+ history = self._session.get(sender)
102
+
103
+ # --- Dispatch to Gateway via executor (non-blocking) ---
104
+ loop = asyncio.get_running_loop()
105
+ response: str = await loop.run_in_executor(
106
+ None,
107
+ lambda: self._gateway.send(mapped, history=history),
108
+ )
109
+
110
+ # --- Disambiguation: multiple modes detected ---
111
+ if response.startswith("Multiple modes detected"):
112
+ self._clarification[sender] = mapped
113
+ await c.send(response)
114
+ return
115
+
116
+ # --- Update session and send response ---
117
+ self._session.update(sender, mapped, response)
118
+ await c.send(strip_markdown(response))
119
+
120
+ async def _handle_clarification(
121
+ self, c: Any, sender: str, reply: str
122
+ ) -> None:
123
+ """Process a clarification reply (offensive / defensive).
124
+
125
+ Maps the reply to a mode prefix, re-routes the stored query.
126
+ Keeps state if the reply is unrecognized.
127
+ """
128
+ reply_lower = reply.strip().lower()
129
+
130
+ # Find matching mode keyword
131
+ matched_mode: str | None = None
132
+ for keyword, mode in _CLARIFY_MAP.items():
133
+ if keyword in reply_lower:
134
+ matched_mode = mode
135
+ break
136
+
137
+ if matched_mode is None:
138
+ await c.send("Please reply 'offensive' or 'defensive'.")
139
+ return
140
+
141
+ # Route stored query with resolved prefix
142
+ original_query = self._clarification.pop(sender)
143
+ prefixed = f"[MODE: {matched_mode}] {original_query}"
144
+
145
+ await c.react("🧠")
146
+
147
+ history = self._session.get(sender)
148
+ loop = asyncio.get_running_loop()
149
+ response: str = await loop.run_in_executor(
150
+ None,
151
+ lambda: self._gateway.send(prefixed, history=history),
152
+ )
153
+
154
+ self._session.update(sender, prefixed, response)
155
+ await c.send(strip_markdown(response))
156
+
157
+
158
+ # ---------------------------------------------------------------------------
159
+ # Bot runner
160
+ # ---------------------------------------------------------------------------
161
+
162
+ def run_bot(config: dict[str, Any]) -> None:
163
+ """Construct and start the SignalBot with CipherCommand registered.
164
+
165
+ Args:
166
+ config: Signal config dict (signal_service, phone_number, whitelist,
167
+ session_timeout).
168
+ """
169
+ from signalbot import SignalBot, Config # type: ignore[import]
170
+
171
+ signal_cfg = Config(
172
+ signal_service=config["signal_service"],
173
+ phone_number=config["phone_number"],
174
+ )
175
+ bot = SignalBot(signal_cfg)
176
+ gateway = Gateway()
177
+ session = SessionManager(
178
+ timeout_seconds=int(config.get("session_timeout", 3600))
179
+ )
180
+ cmd = CipherCommand(
181
+ gateway=gateway,
182
+ session=session,
183
+ whitelist=set(config.get("whitelist", [])),
184
+ )
185
+ bot.register(cmd)
186
+ bot.start()
187
+
188
+
189
+ # ---------------------------------------------------------------------------
190
+ # Main with watchdog
191
+ # ---------------------------------------------------------------------------
192
+
193
+ def main() -> None:
194
+ """Entry point: load config, run bot with exponential-backoff watchdog."""
195
+ logging.basicConfig(
196
+ level=logging.INFO,
197
+ format="%(asctime)s %(levelname)s %(name)s β€” %(message)s",
198
+ )
199
+
200
+ from gateway.config import _find_project_root, _load_yaml_file
201
+ from pathlib import Path
202
+
203
+ # Locate config.yaml
204
+ project_root = _find_project_root(Path(__file__).parent)
205
+ if project_root is None:
206
+ project_root = Path.cwd()
207
+
208
+ raw = _load_yaml_file(Path(project_root) / "config.yaml")
209
+ signal_cfg: dict[str, Any] = raw.get("signal", {})
210
+
211
+ # Env var overrides
212
+ import os
213
+ if sv := os.environ.get("SIGNAL_SERVICE"):
214
+ signal_cfg["signal_service"] = sv
215
+ if spn := os.environ.get("SIGNAL_PHONE_NUMBER"):
216
+ signal_cfg["phone_number"] = spn
217
+ if swl := os.environ.get("SIGNAL_WHITELIST"):
218
+ signal_cfg["whitelist"] = [n.strip() for n in swl.split(",") if n.strip()]
219
+
220
+ delay = 1.0
221
+ while True:
222
+ try:
223
+ logger.info("Starting bot …")
224
+ run_bot(signal_cfg)
225
+ # Clean exit β€” reset delay
226
+ delay = 1.0
227
+ except Exception as exc: # noqa: BLE001
228
+ logger.error("Bot crashed: %s β€” retrying in %.0fs", exc, delay)
229
+ time.sleep(delay)
230
+ delay = min(delay * 2, 60.0)
231
+
232
+
233
+ if __name__ == "__main__":
234
+ main()
bot/format.py ADDED
@@ -0,0 +1,84 @@
1
+ # Copyright (c) 2026 defconxt. All rights reserved.
2
+ # Licensed under AGPL-3.0 β€” see LICENSE file for details.
3
+ """Bot formatting utilities.
4
+
5
+ map_prefix: Maps short Signal prefix (e.g. 'RED: query') to gateway format
6
+ ('[MODE: RED] query'). Passes through text with no recognized prefix.
7
+
8
+ strip_markdown: Strips markdown formatting from LLM responses for plaintext
9
+ Signal delivery. Preserves structure via line breaks and ASCII bullets.
10
+ """
11
+
12
+ import re
13
+
14
+ # ---------------------------------------------------------------------------
15
+ # Prefix mapping
16
+ # ---------------------------------------------------------------------------
17
+
18
+ _PREFIX_RE = re.compile(
19
+ r"^(RED|BLUE|PURPLE|PRIVACY|RECON|INCIDENT|ARCHITECT):\s*",
20
+ re.IGNORECASE,
21
+ )
22
+
23
+
24
+ def map_prefix(text: str) -> str:
25
+ """Map 'MODE: query' short-prefix format to '[MODE: MODE] query'.
26
+
27
+ Case-insensitive. Strips optional whitespace after colon.
28
+ Returns text unchanged when no recognized mode prefix is found.
29
+
30
+ Examples:
31
+ map_prefix("RED: query") -> "[MODE: RED] query"
32
+ map_prefix("blue: what is DNS") -> "[MODE: BLUE] what is DNS"
33
+ map_prefix("RED:query") -> "[MODE: RED] query"
34
+ map_prefix("no prefix here") -> "no prefix here"
35
+ """
36
+ m = _PREFIX_RE.match(text)
37
+ if m:
38
+ mode = m.group(1).upper()
39
+ rest = text[m.end():]
40
+ return f"[MODE: {mode}] {rest}"
41
+ return text
42
+
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # Markdown stripping
46
+ # ---------------------------------------------------------------------------
47
+
48
+ # Patterns applied in order β€” order matters (bold before italic, code blocks
49
+ # before inline code).
50
+ _MD_PATTERNS: list[tuple[re.Pattern[str], str]] = [
51
+ # Fenced code blocks: ```lang\ncontent\n``` -> content
52
+ (re.compile(r"```[^\n]*\n(.*?)```", re.DOTALL), r"\1"),
53
+ # Headers: ## Heading -> Heading
54
+ (re.compile(r"^#{1,6}\s+", re.MULTILINE), ""),
55
+ # Bold: **text** -> text (must be before italic)
56
+ (re.compile(r"\*{2}([^*\n]+)\*{2}"), r"\1"),
57
+ # Italic: *text* -> text
58
+ (re.compile(r"\*([^*\n]+)\*"), r"\1"),
59
+ # Inline code: `code` -> code
60
+ (re.compile(r"`([^`\n]+)`"), r"\1"),
61
+ # Links: [text](url) -> text
62
+ (re.compile(r"\[([^\]]+)\]\([^)]+\)"), r"\1"),
63
+ # Bullets: "* item" or "- item" -> "β€’ item" (preserves leading whitespace)
64
+ (re.compile(r"^(\s*)[*-]\s+", re.MULTILINE), r"\1β€’ "),
65
+ ]
66
+
67
+
68
+ def strip_markdown(text: str) -> str:
69
+ """Strip markdown formatting for plaintext Signal delivery.
70
+
71
+ Removes headers, bold, italic, code blocks, inline code, links.
72
+ Converts markdown bullets (* / -) to ASCII bullets (β€’).
73
+ Preserves plain text, newlines, and indentation.
74
+
75
+ Examples:
76
+ strip_markdown("## Heading") -> "Heading"
77
+ strip_markdown("**bold** text") -> "bold text"
78
+ strip_markdown("```python\\ncode\\n```") -> "code"
79
+ strip_markdown("* item") -> "β€’ item"
80
+ strip_markdown("[link](http://x)") -> "link"
81
+ """
82
+ for pattern, replacement in _MD_PATTERNS:
83
+ text = pattern.sub(replacement, text)
84
+ return text.strip()
bot/session.py ADDED
@@ -0,0 +1,98 @@
1
+ # Copyright (c) 2026 defconxt. All rights reserved.
2
+ # Licensed under AGPL-3.0 β€” see LICENSE file for details.
3
+ """Session management for the CIPHER Signal bot.
4
+
5
+ SessionManager maintains per-sender conversation history compatible with
6
+ Gateway.send(history=) format. Sessions expire after configurable inactivity
7
+ and are capped at a maximum number of message pairs.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from datetime import datetime, timezone, timedelta
13
+
14
+
15
+ class SessionManager:
16
+ """In-memory conversation history manager keyed by sender phone number.
17
+
18
+ Session dict structure per sender:
19
+ {
20
+ "history": [{"role": "user"|"assistant", "content": "..."}, ...],
21
+ "last_active": datetime (UTC),
22
+ }
23
+
24
+ History format matches Gateway.send(history=) β€” list of role/content dicts.
25
+
26
+ Args:
27
+ timeout_seconds: Inactivity window before a session is expired.
28
+ Default 3600 (1 hour).
29
+ max_pairs: Maximum number of user/assistant pairs to retain.
30
+ Oldest pair is evicted when the cap is exceeded. Default 20.
31
+ """
32
+
33
+ def __init__(self, timeout_seconds: int = 3600, max_pairs: int = 20) -> None:
34
+ self._sessions: dict[str, dict] = {}
35
+ self._timeout = timeout_seconds
36
+ self._max_pairs = max_pairs
37
+
38
+ # ------------------------------------------------------------------
39
+ # Public API
40
+ # ------------------------------------------------------------------
41
+
42
+ def get(self, sender: str) -> list[dict]:
43
+ """Return conversation history for sender.
44
+
45
+ Calls cleanup() first to expire stale sessions.
46
+ Returns empty list for unknown or expired senders.
47
+ """
48
+ self.cleanup()
49
+ session = self._sessions.get(sender)
50
+ if session is None:
51
+ return []
52
+ return list(session["history"])
53
+
54
+ def update(self, sender: str, user_message: str, assistant_message: str) -> None:
55
+ """Append a user/assistant pair to sender's history.
56
+
57
+ Creates the session if it does not exist. Updates last_active.
58
+ Evicts the oldest pair if the cap is exceeded.
59
+ """
60
+ if sender not in self._sessions:
61
+ self._sessions[sender] = {"history": [], "last_active": self._now()}
62
+
63
+ session = self._sessions[sender]
64
+ session["history"].append({"role": "user", "content": user_message})
65
+ session["history"].append({"role": "assistant", "content": assistant_message})
66
+ session["last_active"] = self._now()
67
+
68
+ # Enforce cap: keep only the newest max_pairs pairs (max_pairs * 2 items)
69
+ max_items = self._max_pairs * 2
70
+ if len(session["history"]) > max_items:
71
+ session["history"] = session["history"][-max_items:]
72
+
73
+ def reset(self, sender: str) -> None:
74
+ """Clear conversation history for sender.
75
+
76
+ No-op if sender has no active session.
77
+ """
78
+ self._sessions.pop(sender, None)
79
+
80
+ def cleanup(self) -> None:
81
+ """Remove sessions that have been inactive longer than timeout_seconds."""
82
+ now = self._now()
83
+ cutoff = timedelta(seconds=self._timeout)
84
+ stale = [
85
+ sender
86
+ for sender, session in self._sessions.items()
87
+ if (now - session["last_active"]) > cutoff
88
+ ]
89
+ for sender in stale:
90
+ del self._sessions[sender]
91
+
92
+ # ------------------------------------------------------------------
93
+ # Internal helpers
94
+ # ------------------------------------------------------------------
95
+
96
+ @staticmethod
97
+ def _now() -> datetime:
98
+ return datetime.now(timezone.utc)
@@ -0,0 +1,249 @@
1
+ Metadata-Version: 2.4
2
+ Name: cipher-security
3
+ Version: 0.2.0
4
+ Summary: CIPHER β€” Security Engineering Assistant with RAG-powered knowledge base
5
+ Project-URL: Homepage, https://github.com/defconxt/CIPHER
6
+ Project-URL: Documentation, https://blacktemple.net/cipher
7
+ Project-URL: Repository, https://github.com/defconxt/CIPHER
8
+ Project-URL: Issues, https://github.com/defconxt/CIPHER/issues
9
+ Author-email: defconxt <trevor@blacktemple.net>
10
+ License-Expression: AGPL-3.0-only
11
+ License-File: LICENSE
12
+ Keywords: ATT&CK,SIEM,cybersecurity,detection,pentest,security,threat-hunting
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Information Technology
16
+ Classifier: Intended Audience :: System Administrators
17
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Security
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: anthropic>=0.84.0
27
+ Requires-Dist: chromadb>=1.0.0
28
+ Requires-Dist: pyyaml>=6.0
29
+ Requires-Dist: rich>=13.0
30
+ Requires-Dist: textual>=1.0
31
+ Requires-Dist: typer>=0.12
32
+ Provides-Extra: all
33
+ Requires-Dist: signalbot>=0.25.0; extra == 'all'
34
+ Provides-Extra: signal
35
+ Requires-Dist: signalbot>=0.25.0; extra == 'signal'
36
+ Description-Content-Type: text/markdown
37
+
38
+ <!-- Copyright (c) 2026 defconxt. All rights reserved. -->
39
+ <!-- Licensed under AGPL-3.0 β€” see LICENSE file for details. -->
40
+ <!-- CIPHER is a trademark of defconxt. -->
41
+
42
+ <div align="center">
43
+
44
+ ```
45
+ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
46
+ β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—
47
+ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•
48
+ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—
49
+ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘
50
+ β•šβ•β•β•β•β•β•β•šβ•β•β•šβ•β• β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β•β•šβ•β• β•šβ•β•
51
+ ```
52
+
53
+ **Security Engineering Assistant**
54
+
55
+ [![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
56
+ [![Tests](https://github.com/defconxt/CIPHER/actions/workflows/ci.yml/badge.svg)](https://github.com/defconxt/CIPHER/actions)
57
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-00ff41.svg)](https://python.org)
58
+
59
+ A principal-level security engineering assistant with 96 deep-dive knowledge docs,
60
+ RAG-powered retrieval, 7 operating modes, and 28 specialized skills.
61
+ Runs locally via Ollama or cloud via Claude API.
62
+
63
+ [Install](#install) Β· [Quick Start](#quick-start) Β· [Features](#features) Β· [Documentation](https://blacktemple.net/cipher)
64
+
65
+ </div>
66
+
67
+ ---
68
+
69
+ ## Install
70
+
71
+ **One-liner (Linux/macOS):**
72
+ ```bash
73
+ curl -fsSL https://blacktemple.net/cipher/install.sh | bash
74
+ ```
75
+
76
+ **Windows PowerShell:**
77
+ ```powershell
78
+ iwr -useb https://blacktemple.net/cipher/install.ps1 | iex
79
+ ```
80
+
81
+ > **Note:** Install scripts also available at `https://raw.githubusercontent.com/defconxt/CIPHER/main/scripts/install.sh`
82
+
83
+ **pip:**
84
+ ```bash
85
+ pip install cipher-security
86
+ cipher setup
87
+ ```
88
+
89
+ **Docker:**
90
+ ```bash
91
+ docker run -it ghcr.io/defconxt/cipher "how do I detect Kerberoasting"
92
+ ```
93
+
94
+ ## Quick Start
95
+
96
+ ```bash
97
+ # Ask anything security
98
+ cipher "how do I detect lateral movement via PsExec"
99
+
100
+ # Force a specific mode
101
+ cipher "[MODE: RED] exploit AS-REP roasting in Active Directory"
102
+
103
+ # Auto-route: simple queries β†’ local Ollama, complex β†’ Claude API
104
+ cipher --smart "design a zero trust architecture for multi-cloud"
105
+
106
+ # Interactive dashboard
107
+ cipher dashboard
108
+
109
+ # System health check
110
+ cipher doctor
111
+ ```
112
+
113
+ ## Features
114
+
115
+ ### 7 Operating Modes
116
+
117
+ | Mode | Focus | Triggers |
118
+ |------|-------|----------|
119
+ | **RED** | Offensive security, attack paths, exploitation | exploit, payload, privesc, C2, red team |
120
+ | **BLUE** | Detection, SIEM, hardening, threat hunting | detection, Sigma, SIEM, EDR, hardening |
121
+ | **PURPLE** | Adversary emulation, coverage mapping | emulation, ATT&CK mapping, gap analysis |
122
+ | **PRIVACY** | GDPR, CCPA, HIPAA, DPIAs, anonymization | GDPR, CCPA, HIPAA, DPIA, privacy |
123
+ | **RECON** | OSINT, reconnaissance, footprinting | OSINT, recon, subdomain, footprinting |
124
+ | **INCIDENT** | IR playbooks, forensics, containment | triage, incident response, IOC, forensics |
125
+ | **ARCHITECT** | Zero trust, threat modeling, design | design, architecture, threat model, zero trust |
126
+
127
+ Modes auto-detect from your query. Ambiguous queries prompt for clarification.
128
+
129
+ ### 96 Deep-Dive Knowledge Docs
130
+
131
+ RAG-powered retrieval over 145K+ lines of security knowledge:
132
+
133
+ - **Offensive**: AD attacks, web exploitation, cloud attacks, C2, shells, evasion techniques
134
+ - **Defensive**: Sigma rules, SIEM/SOC, EDR internals, hardening guides, Windows Event Log mastery
135
+ - **Forensics**: DFIR hunting, forensic artifacts, timeline analysis, memory forensics, email forensics
136
+ - **Architecture**: Threat modeling (STRIDE/PASTA), cloud reference architectures, crypto/PKI/TLS
137
+ - **Compliance**: NIST, CIS, SOC2, ISO 27001, GDPR, PCI DSS, HIPAA
138
+ - **Emerging**: Breach case studies (12 major incidents), AI/ML security, vulnerability research, ransomware ecosystem
139
+
140
+ ### 28 Slash Commands (Claude Code)
141
+
142
+ ```
143
+ /cipher:redteam /cipher:web /cipher:phishing /cipher:malware
144
+ /cipher:hunt /cipher:sigma /cipher:hardening /cipher:forensics
145
+ /cipher:purple /cipher:recon /cipher:privacy /cipher:insider
146
+ /cipher:threatmodel /cipher:cloud /cipher:crypto /cipher:devsecops
147
+ /cipher:ir /cipher:cve /cipher:threatintel /cipher:aisec
148
+ /cipher:audit /cipher:report /cipher:ics /cipher:mobile
149
+ /cipher:cc /cipher:ollama /cipher:claudeapi /cipher:smart
150
+ ```
151
+
152
+ ### Multiple Interfaces
153
+
154
+ | Interface | Command | Cost |
155
+ |-----------|---------|------|
156
+ | **CLI** | `cipher "query"` | Free (Ollama) or paid (Claude) |
157
+ | **Dashboard** | `cipher dashboard` | Free (Ollama) or paid (Claude) |
158
+ | **Claude Code** | `/cipher:cc "query"` | Included with Claude Code |
159
+ | **Signal Bot** | Message the bot | Free (Ollama) or paid (Claude) |
160
+ | **Docker** | `docker run -it cipher "query"` | Free (Ollama) or paid (Claude) |
161
+
162
+ ### RAG Pipeline
163
+
164
+ Semantic search over the entire knowledge base:
165
+ - **14,600+ chunks** indexed in ChromaDB
166
+ - Markdown-aware chunking preserving section context
167
+ - Top-5 retrieval injected into system prompt before every query
168
+ - Auto re-ingests on knowledge base changes (git hook)
169
+
170
+ ## CLI Commands
171
+
172
+ ```
173
+ cipher "query" Send a security query (default command)
174
+ cipher --smart "query" Auto-route: simple→Ollama, complex→Claude
175
+ cipher --backend ollama Force local inference
176
+ cipher --backend claude Force cloud inference
177
+ cipher setup Interactive onboarding wizard
178
+ cipher dashboard Cyberpunk terminal UI
179
+ cipher doctor Diagnostics and health checks
180
+ cipher doctor --fix Auto-repair issues
181
+ cipher status Show system status
182
+ cipher ingest Re-index the knowledge base
183
+ cipher version Show version
184
+ ```
185
+
186
+ ## Architecture
187
+
188
+ ```
189
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
190
+ β”‚ Interfaces β”‚
191
+ β”‚ CLI Β· Dashboard Β· Claude Code Β· Signal Bot Β· Docker β”‚
192
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
193
+ β”‚
194
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
195
+ β”‚ Gateway β”‚
196
+ β”‚ Mode Detection │──── 7 modes
197
+ β”‚ Prompt Assembly│──── 13 skill files
198
+ β”‚ RAG Retrieval │──── 14,600 chunks
199
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
200
+ β”‚
201
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
202
+ β”‚ β”‚
203
+ β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
204
+ β”‚ Ollama β”‚ β”‚ Claude API β”‚
205
+ β”‚ (local) β”‚ β”‚ (cloud) β”‚
206
+ β”‚ qwen2.5:32b β”‚ β”‚ sonnet-4-5 β”‚
207
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
208
+ ```
209
+
210
+ ## Configuration
211
+
212
+ Config file: `config.yaml` (project root) or `~/.config/cipher/config.yaml`
213
+
214
+ Supports `${VAR}` and `${VAR:-default}` environment variable substitution:
215
+
216
+ ```yaml
217
+ llm_backend: ollama
218
+
219
+ ollama:
220
+ base_url: "http://127.0.0.1:11434"
221
+ model: "cipher"
222
+ timeout: 300
223
+
224
+ claude:
225
+ api_key: "${ANTHROPIC_API_KEY}"
226
+ model: "claude-sonnet-4-5-20250929"
227
+ timeout: 120
228
+ ```
229
+
230
+ Priority: env vars > project config > home config.
231
+
232
+ ## Development
233
+
234
+ ```bash
235
+ git clone https://github.com/defconxt/CIPHER.git && cd CIPHER
236
+ python -m venv .venv && source .venv/bin/activate
237
+ pip install -e ".[signal]"
238
+ pytest tests/ --ignore=tests/test_integration.py
239
+ ```
240
+
241
+ **318 tests** covering gateway, mode routing, RAG quality, architecture guardrails, and configuration.
242
+
243
+ ## Contributing
244
+
245
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. Security researchers welcome.
246
+
247
+ ## License
248
+
249
+ [AGPL-3.0](LICENSE) β€” CIPHER is a trademark of [defconxt](https://github.com/defconxt).
@@ -0,0 +1,20 @@
1
+ bot/__init__.py,sha256=YCaNS9AnlUTyxgrZc9bPrLDq73Eo8fuOV0vOCsNl2WQ,40
2
+ bot/bot.py,sha256=44GAM6yNz7Cv-vMuYTvBiRh9hcnF8k81Y9wo6wcABAQ,7589
3
+ bot/format.py,sha256=GMmhzUczKsRkM571GIcAMOjV1RbKDeYP4VyXIcyNU8c,3177
4
+ bot/session.py,sha256=KZe8l0qsEYDuGYIaTvyLE5MLMvRmHO1vsZOaTJSjzUY,3609
5
+ gateway/__init__.py,sha256=Fcp_qiUotdefmhau1Hx5QteL5naU883v77wqHqsBVUw,347
6
+ gateway/app.py,sha256=prVguxv5AHDL2Yirb75m85lJu0dF9ojzmpSO42RuD1A,22208
7
+ gateway/cli.py,sha256=IWji6x-EipmOGdHoqjy-4BTO3C-8BbM9c6cXr4xy5qw,9881
8
+ gateway/client.py,sha256=GY8a9ZG4R6bJXB-Yp-_AtNDMyP5R7Dd-ANb1jYBmddo,1968
9
+ gateway/config.py,sha256=JBM2jURybnk3X9KuaRganQ9PlSBX_MM2Z-5iPXWgy0A,7757
10
+ gateway/dashboard.py,sha256=CATXWb7laVgj9qI37qSH1MmJJL6SopMQn5MWXWMBonI,11007
11
+ gateway/gateway.py,sha256=0Glq-4BVT60HDUeAcZbNJYrfLdOhd5I6jpVyh5MdfdA,6783
12
+ gateway/mode.py,sha256=aYkbFIK4PZNpirm6EhHWZcoeqj1DhRHmntbjaKQLJ-I,4652
13
+ gateway/prompt.py,sha256=C6vrkWlsbCEwkSsy6AGOmtL_bdXhBthM5taQj9HoXDE,5408
14
+ gateway/retriever.py,sha256=XL_td4qe2hBfQbbBeDW537KHOSdgKMkmiDkt7YdW5nU,8776
15
+ gateway/theme.py,sha256=mlXKIJBkvIaHqnm4nw_-1tgCiaRrUnC-b7rp0qRNSLU,3471
16
+ cipher_security-0.2.0.dist-info/METADATA,sha256=g1_g2QTYFPrd8KxhUJ_1zOsWeJnAAQcH6cNX91VJ2wg,9835
17
+ cipher_security-0.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
18
+ cipher_security-0.2.0.dist-info/entry_points.txt,sha256=7NpFwFl9KHaGgHRCboynB1itMHkTHe2YLXzH4GHCHq8,99
19
+ cipher_security-0.2.0.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
20
+ cipher_security-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any