feat(release): Loxide 1.0 RC

- Added comprehensive documentation:
  - System Design Requirements (SDR)
  - System Design Specification (SDS)
  - API Reference
  - User Stories & Use Cases
- Fixed minor UI issues related to double encoding
This commit is contained in:
2025-12-22 10:45:36 -05:00
parent e1e0cb7ac7
commit 423e9e8208
8 changed files with 3416 additions and 22 deletions
+1 -1
View File
@@ -51,6 +51,7 @@ import airlock_libs
from models.agent import Agent from models.agent import Agent
from models.policy import Policy from models.policy import Policy
from services.API import AirlockAPIWrapper from services.API import AirlockAPIWrapper
from services.security import getAPI
from TUI.Screens.executionhistoryscreen import ExecutionHistoryScreen from TUI.Screens.executionhistoryscreen import ExecutionHistoryScreen
from TUI.Screens.moveagentworkflowscreen import MoveAgentWorkflowScreen from TUI.Screens.moveagentworkflowscreen import MoveAgentWorkflowScreen
from TUI.Screens.otpactivityscreen import OTPActivitiesScreen from TUI.Screens.otpactivityscreen import OTPActivitiesScreen
@@ -70,7 +71,6 @@ from utils.configmanager import (
load_env, load_env,
save_user_config, save_user_config,
) )
from utils.security import getAPI
from utils.setup import get_base_directory, setup from utils.setup import get_base_directory, setup
from utils.utils import irtang, open_directory from utils.utils import irtang, open_directory
+20 -20
View File
@@ -273,11 +273,11 @@ class AgentMoveOperations(Widget):
results_lines.append(" (none)") results_lines.append(" (none)")
results_lines.append("") results_lines.append("")
results_lines.append(f"❌ Failed ({len(unsuccessful)}):") results_lines.append(f" Failed ({len(unsuccessful)}):")
if unsuccessful: if unsuccessful:
for agent, error in unsuccessful: for agent, error in unsuccessful:
results_lines.append(f" ❌ {agent.hostname}: {error}") results_lines.append(f" {agent.hostname}: {error}")
else: else:
results_lines.append(" (none)") results_lines.append(" (none)")
@@ -312,9 +312,9 @@ class AgentMoveOperations(Widget):
- Operations panel: 1/3 width - Operations panel: 1/3 width
- Results area: Initially hidden, shown after operation completion - Results area: Initially hidden, shown after operation completion
""" """
yield Header(show_clock=True, icon="âš™❗") yield Header(show_clock=True, icon="⚙️")
title_text = Static( title_text = Static(
f"🖥❗ Agent Operations - {len(self.agents)} device(s) selected", f"🖥️ Agent Operations - {len(self.agents)} device(s) selected",
id="move_ops_title", id="move_ops_title",
) )
title_text.styles.margin = (0, 0, 1, 0) title_text.styles.margin = (0, 0, 1, 0)
@@ -349,39 +349,39 @@ class AgentMoveOperations(Widget):
yield operations_label yield operations_label
# Operation buttons # Operation buttons
export_csv_btn = Button("📄 Export CSV", id="export_csv_btn") export_csv_btn = Button("📄 Export CSV", id="export_csv_btn")
export_csv_btn.styles.width = "100%" export_csv_btn.styles.width = "100%"
export_csv_btn.styles.margin = (0, 0, 1, 0) export_csv_btn.styles.margin = (0, 0, 1, 0)
yield export_csv_btn yield export_csv_btn
local_approval_btn = Button( local_approval_btn = Button(
"✔❗ Local Approval Mode", id="local_approval_btn" "✔️ Local Approval Mode", id="local_approval_btn"
) )
local_approval_btn.styles.width = "100%" local_approval_btn.styles.width = "100%"
local_approval_btn.styles.margin = (0, 0, 1, 0) local_approval_btn.styles.margin = (0, 0, 1, 0)
yield local_approval_btn yield local_approval_btn
otp_gen_btn = Button("🎫 Generate One Time Passes", id="otp_gen_btn") otp_gen_btn = Button("🎫 Generate One Time Passes", id="otp_gen_btn")
otp_gen_btn.styles.width = "100%" otp_gen_btn.styles.width = "100%"
otp_gen_btn.styles.margin = (0, 0, 1, 0) otp_gen_btn.styles.margin = (0, 0, 1, 0)
yield otp_gen_btn yield otp_gen_btn
toggle_enforcement_btn = Button( toggle_enforcement_btn = Button(
"🔄 Toggle Audit/Enforcement", id="toggle_enforcement_btn" "🔄 Toggle Audit/Enforcement", id="toggle_enforcement_btn"
) )
toggle_enforcement_btn.styles.width = "100%" toggle_enforcement_btn.styles.width = "100%"
toggle_enforcement_btn.styles.margin = (0, 0, 1, 0) toggle_enforcement_btn.styles.margin = (0, 0, 1, 0)
yield toggle_enforcement_btn yield toggle_enforcement_btn
other_policy_btn = Button( other_policy_btn = Button(
"🔀 Move to Other Policy", id="other_policy_btn" "🔀 Move to Other Policy", id="other_policy_btn"
) )
other_policy_btn.styles.width = "100%" other_policy_btn.styles.width = "100%"
other_policy_btn.styles.margin = (0, 0, 1, 0) other_policy_btn.styles.margin = (0, 0, 1, 0)
yield other_policy_btn yield other_policy_btn
exec_history_btn = Button( exec_history_btn = Button(
"📊 View Execution History", id="exec_history_btn" "📊 View Execution History", id="exec_history_btn"
) )
exec_history_btn.styles.width = "100%" exec_history_btn.styles.width = "100%"
exec_history_btn.styles.margin = (0, 0, 1, 0) exec_history_btn.styles.margin = (0, 0, 1, 0)
@@ -448,17 +448,17 @@ class AgentMoveOperations(Widget):
pyperclip.copy(results_text.text) pyperclip.copy(results_text.text)
self.app.notify( self.app.notify(
"📋✅ Results copied to clipboard!", "📋✅ Results copied to clipboard!",
severity="information", severity="information",
timeout=2, timeout=2,
) )
except ImportError: except ImportError:
self.app.notify( self.app.notify(
"❌ pyperclip not installed. Run: pip install pyperclip", " pyperclip not installed. Run: pip install pyperclip",
severity="warning", severity="warning",
) )
except Exception as e: except Exception as e:
self.app.notify(f"❌ Failed to copy: {str(e)}", severity="error") self.app.notify(f" Failed to copy: {str(e)}", severity="error")
event.stop() event.stop()
elif btn_id == "export_csv_btn": elif btn_id == "export_csv_btn":
self._start_export_csv_operation() self._start_export_csv_operation()
@@ -509,7 +509,7 @@ class AgentMoveOperations(Widget):
self.operation_in_progress = True self.operation_in_progress = True
status_label = self.query_one("#status_label", Static) status_label = self.query_one("#status_label", Static)
status_label.update("✔❗ Moving agents to local approval...") status_label.update("✔️ Moving agents to local approval...")
# Get API from app # Get API from app
api = self.app.api api = self.app.api
@@ -542,7 +542,7 @@ class AgentMoveOperations(Widget):
except Exception as e: except Exception as e:
logger.error(f"Error during local approval operation: {e}") logger.error(f"Error during local approval operation: {e}")
status_label.update(f"❌ Error: {str(e)}") status_label.update(f" Error: {str(e)}")
self.operation_in_progress = False self.operation_in_progress = False
return return
@@ -592,7 +592,7 @@ class AgentMoveOperations(Widget):
successful.append(file_path) successful.append(file_path)
status_label.update(f"✅ Exported to {file_path}") status_label.update(f"✅ Exported to {file_path}")
except Exception: except Exception:
status_label.update("❌ Failed") status_label.update(" Failed")
self.operation_in_progress = False self.operation_in_progress = False
@@ -636,7 +636,7 @@ class AgentMoveOperations(Widget):
self.operation_in_progress = True self.operation_in_progress = True
status_label = self.query_one("#status_label", Static) status_label = self.query_one("#status_label", Static)
status_label.update("🔄 Toggling enforcement mode...") status_label.update("🔄 Toggling enforcement mode...")
# Get API from app # Get API from app
api = self.app.api api = self.app.api
@@ -669,7 +669,7 @@ class AgentMoveOperations(Widget):
except Exception as e: except Exception as e:
logger.error(f"Error during toggle enforcement operation: {e}") logger.error(f"Error during toggle enforcement operation: {e}")
status_label.update(f"❌ Error: {str(e)}") status_label.update(f" Error: {str(e)}")
self.operation_in_progress = False self.operation_in_progress = False
return return
@@ -739,7 +739,7 @@ class AgentMoveOperations(Widget):
except Exception as e: except Exception as e:
logger.error(f"Error loading policies: {e}") logger.error(f"Error loading policies: {e}")
status_label.update(f"❌ Error: {str(e)}") status_label.update(f" Error: {str(e)}")
self.operation_in_progress = False self.operation_in_progress = False
self.selected_operation = "" self.selected_operation = ""
self.app.notify(f"Failed to load policies: {str(e)}", severity="error") self.app.notify(f"Failed to load policies: {str(e)}", severity="error")
@@ -774,7 +774,7 @@ class AgentMoveOperations(Widget):
) )
except Exception as e: except Exception as e:
logger.error(f"Failed to open execution history viewer: {e}") logger.error(f"Failed to open execution history viewer: {e}")
status_label.update(f"❌ Error: {str(e)}") status_label.update(f" Error: {str(e)}")
self.app.notify( self.app.notify(
f"Failed to open execution history: {str(e)}", severity="error" f"Failed to open execution history: {str(e)}", severity="error"
) )
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+476
View File
@@ -0,0 +1,476 @@
# Loxide
## API Reference Document
**Version 1.0 | December 2025**
---
## 1. Overview
This document provides a complete reference for the AirlockAPIWrapper class and all Airlock API endpoints used by Loxide.
### 1.1 Authentication
All API requests require an API key passed via the X-APIKey header:
```
Headers: { "X-APIKey": "your-api-key-here" }
```
### 1.2 Base URL
The base URL is configured in `system_config.json` and typically follows the pattern:
```
https://airlock.example.com/api
```
---
## 2. Agent Management
### 2.1 agent_find_all()
Retrieve all registered agents.
**Endpoint:** `POST /v1/agent/find`
**Payload:** `{}`
**Returns:** DataFrame with agent records
| Field | Type | Description |
|-------|------|-------------|
| `hostname` | str | Device hostname |
| `agentid` | str | Unique identifier |
| `clientversion` | str | Agent version |
| `groupid` | str | Policy group ID |
| `status` | int | 0=Offline, 1=Online, 2=Hidden, 3=Safemode |
| `lastcheckin` | str | Last check-in timestamp |
| `ip` | str | External IP address |
| `localip` | str | Internal IP address |
| `domain` | str | Network domain |
| `os` | str | Operating system |
| `username` | str | Logged-in user |
| `freespace` | int | Available disk space |
| `policyversion` | str | Active policy version |
### 2.2 agent_find_by_hostname(hostname: str)
Find agents matching a hostname pattern.
**Endpoint:** `POST /v1/agent/find`
**Payload:** `{ "hostname": "<pattern>" }`
**Returns:** DataFrame with matching agents
### 2.3 agent_find_by_id(agentid: str)
Find agent by unique ID.
**Endpoint:** `POST /v1/agent/find`
**Payload:** `{ "agentid": "<id>" }`
**Returns:** DataFrame with agent record
### 2.4 agent_find_by_status(status: int)
Find agents by status code.
**Endpoint:** `POST /v1/agent/find`
**Payload:** `{ "status": <code> }`
**Status Codes:**
- `0` - Offline
- `1` - Online
- `2` - Hidden
- `3` - Safemode
### 2.5 agent_move(agentid: str, groupid: str)
Move an agent to a different policy group.
**Endpoint:** `POST /v1/agent/move`
**Payload:** `{ "agentid": "<id>", "groupid": "<target_group>" }`
**Returns:** dict with operation result
### 2.6 agents_find_by_group(groupid: str)
Find all agents in a policy group.
**Endpoint:** `POST /v1/agent/find`
**Payload:** `{ "groupid": "<id>" }`
**Returns:** DataFrame with agents
---
## 3. Policy Management
### 3.1 policy_find_all()
Retrieve all policy groups.
**Endpoint:** `POST /v1/group`
**Payload:** `{}`
**Returns:** DataFrame with policy records
| Field | Type | Description |
|-------|------|-------------|
| `name` | str | Policy display name |
| `groupid` | int | Unique group identifier |
| `hidden` | bool | Visibility flag |
| `parent` | str | Parent policy name (if child) |
### 3.2 policy_set_auditmode(groupid: str, auditmode: str)
Toggle policy between audit and enforcement modes.
**Endpoint:** `POST /v1/group/settings/auditmode`
**Payload:** `{ "groupid": "<id>", "auditmode": "<mode>" }`
**Mode Values:**
- `"1"` - Audit mode (log only)
- `"0"` - Enforcement mode (block)
### 3.3 policy_list_agents(groupid: str)
List all agents assigned to a policy group.
**Endpoint:** `POST /v1/group/agents`
**Payload:** `{ "groupid": "<id>" }`
**Returns:** DataFrame with agents
### 3.4 policy_list_allowlists(groupid: str)
List allowlists assigned to a policy group.
**Endpoint:** `POST /v1/group/policies`
**Payload:** `{ "groupid": "<id>" }`
**Returns:** DataFrame with applications
### 3.5 policy_clone(source_groupid: str, target_groupid: str)
Clone a policy from one group to another.
**Endpoint:** `POST /v1/group/assign`
**Payload:** `{ "groupid": "<source>", "targetgroupid": "<target>" }`
### 3.6 policy_add_path_exclusions(groupid: str, paths: List[str])
Add path exclusions to a policy group.
**Endpoint:** `POST /v1/group/path/add`
**Payload:** `{ "groupid": "<id>", "path": ["<path1>", "<path2>"] }`
### 3.7 policy_add_publishers(groupid: str, publishers: List[str])
Add trusted publishers to a policy group.
**Endpoint:** `POST /v1/group/publisher/add`
**Payload:** `{ "groupid": "<id>", "publisher": ["<pub1>", "<pub2>"] }`
---
## 4. OTP Management
### 4.1 otp_generate(agentid: str, duration: int, purpose: str)
Generate a new One-Time Password for an agent.
**Endpoint:** `POST /v1/otp/retrieve`
**Payload:**
```json
{
"agentid": "<id>",
"duration": "<minutes>",
"purpose": "<description>"
}
```
**Returns:** str - The generated OTP code
### 4.2 otp_find_active()
Retrieve all active OTP sessions.
**Endpoint:** `POST /v1/otp/usage`
**Payload:** `{ "status": "1" }`
**Returns:** DataFrame with OTP records
| Field | Type | Description |
|-------|------|-------------|
| `otpid` | str | OTP session identifier |
| `agentid` | str | Associated agent ID |
| `hostname` | str | Agent hostname |
| `purpose` | str | OTP purpose description |
| `granted` | str | Grant timestamp |
| `expires` | str | Expiration timestamp |
### 4.3 otp_find_awaiting()
Retrieve OTPs awaiting activation.
**Endpoint:** `POST /v1/otp/usage`
**Payload:** `{ "status": "0" }`
### 4.4 otp_find_enforced()
Retrieve enforced OTPs.
**Endpoint:** `POST /v1/otp/usage`
**Payload:** `{ "status": "2" }`
### 4.5 otp_find_revoked()
Retrieve revoked OTPs.
**Endpoint:** `POST /v1/otp/usage`
**Payload:** `{ "status": "3" }`
### 4.6 otp_find_by_agent(agentid: str)
Retrieve OTPs for a specific agent.
**Endpoint:** `POST /v1/otp/usage`
**Payload:** `{ "agentid": "<id>" }`
### 4.7 otp_revoke(otpid: str)
Revoke an active OTP session.
**Endpoint:** `POST /v1/otp/revoke`
**Payload:** `{ "otpid": "<id>" }`
**Returns:** dict with operation result
### 4.8 otp_validate(otpcode: str)
Validate an OTP code.
**Endpoint:** `POST /v1/otp/validate`
**Payload:** `{ "otpcode": "<code>" }`
**Returns:** dict indicating validity
### 4.9 otp_get_activities(otpid: str)
Retrieve activity log for a specific OTP.
**Endpoint:** `POST /v1/otp/activities`
**Payload:** `{ "otpid": "<id>" }`
**Returns:** DataFrame with OTP activities
---
## 5. Execution History
### 5.1 history_execution(today: str, date_selected: str, agent_name: str)
Retrieve execution history for a specific agent.
**Endpoint:** `POST /v1/getexechistory`
**Payload:**
```json
{
"datefrom": "<YYYY-MM-DD>",
"dateto": "<YYYY-MM-DD>",
"hostname": "<name>"
}
```
**Returns:** List[Dict] with execution records
| Field | Type | Description |
|-------|------|-------------|
| `type` | int | Execution type code |
| `hostname` | str | Device hostname |
| `username` | str | User who executed |
| `filename` | str | Executed filename |
| `sha256` | str | File hash |
| `publisher` | str | Code signer |
| `datetime` | str | Execution timestamp |
| `policyname` | str | Active policy |
| `policyver` | str | Policy version |
| `commandline` | str | Full command line |
| `pprocess` | str | Parent process |
### 5.2 Execution Type Codes
| Code | Description |
|------|-------------|
| 0 | Trusted Execution |
| 1 | Blocked Execution |
| 2 | Untrusted Execution [Audit] |
| 3 | Untrusted Execution [OTP] |
| 4 | Trusted Path Execution |
| 5 | Trusted Publisher Execution |
| 6 | Blocklist Execution |
| 7 | Blocklist Execution [Audit] |
| 8 | Trusted Process Execution |
| 9 | Constrained Execution |
| 10 | Trusted Metadata Execution |
| 11 | Trusted Browser Execution |
| 12 | Blocked Browser Execution |
| 13 | Untrusted Browser Execution [Audit] |
| 14 | Untrusted Browser Execution [OTP] |
| 15 | Blocklist Browser Execution [Audit] |
| 16 | Blocklist Browser Execution |
| 17 | Trusted Installer Execution |
| 18 | Trusted Browser Metadata Execution |
### 5.3 history_logging(type: List[str], checkpoint: str, policy: Optional[List[str]])
Retrieve execution history logs with pagination.
**Endpoint:** `POST /v1/logging/exechistories`
**Payload:**
```json
{
"type": ["1", "2", "3"],
"checkpoint": "<checkpoint_id>",
"policy": ["<policy_name>"]
}
```
**Returns:** str with execution histories
---
## 6. Server Logs
### 6.1 server_logs(checkpoint: Optional[str])
Retrieve server activity logs.
**Endpoint:** `POST /v1/logging/svractivities`
**Payload:** `{}` or `{ "checkpoint": "<last_checkpoint>" }`
**Returns:** str with server activities
The checkpoint parameter enables pagination for large result sets. Pass the last checkpoint from a previous call to get subsequent records.
---
## 7. Allowlist and Blocklist Management
### 7.1 allowlist_find_all()
Retrieve all allowlist applications.
**Endpoint:** `POST /v1/application`
**Payload:** `{}`
**Returns:** DataFrame with application records
### 7.2 allowlist_export(applicationid: str)
Export allowlist as XML.
**Endpoint:** `POST /v1/application/export`
**Payload:** `{ "applicationid": "<id>" }`
**Returns:** bytes - XML content
### 7.3 baseline_find_all()
Retrieve all baselines.
**Endpoint:** `POST /v1/baseline`
**Payload:** `{}`
**Returns:** DataFrame with baseline records
### 7.4 baseline_export(baselineid: str)
Export baseline as XML.
**Endpoint:** `POST /v1/baseline/export`
**Payload:** `{ "baselineid": "<id>" }`
**Returns:** bytes - XML content
### 7.5 blocklist_find_all()
Retrieve all blocklists.
**Endpoint:** `POST /v1/blocklist`
**Payload:** `{}`
**Returns:** DataFrame with blocklist records
### 7.6 blocklist_export(blocklistid: str)
Export blocklist as XML.
**Endpoint:** `POST /v1/blocklist/export`
**Payload:** `{ "blocklistid": "<id>" }`
**Returns:** bytes - XML content
### 7.7 hash_add_to_allowlist(applicationid: str, hashes: List[str])
Add hashes to an allowlist.
**Endpoint:** `POST /v1/hash/application/add`
**Payload:** `{ "applicationid": "<id>", "hashes": ["<sha256>", ...] }`
**Returns:** dict with operation result
### 7.8 hash_query(hashes: List[str])
Query information about specific hashes.
**Endpoint:** `POST /v1/hash/query`
**Payload:** `{ "hashes": ["<sha256>", ...] }`
**Returns:** DataFrame with hash records
| Field | Type | Description |
|-------|------|-------------|
| `sha256` | str | Hash value |
| `filename` | str | Associated filename |
| `publisher` | str | Code signer (or "Not Signed") |
| `reputation` | dict | VirusTotal scan results |
| `applications` | str | Associated allowlists |
| `baselines` | str | Associated baselines |
| `blocklists` | str | Associated blocklists |
---
## 8. Rust Backend (airlock_libs)
The `airlock_libs` package provides Rust-accelerated functions for performance-critical operations.
### 8.1 pull_policy_exec_histories(api, type, days, policy_name)
Pull execution history for policies with optimized performance.
| Parameter | Type | Description |
|-----------|------|-------------|
| `api` | AirlockAPIWrapper | API wrapper instance |
| `type` | str | JSON list of exec types, e.g., "[1,2,3]" |
| `days` | int | Days to look back |
| `policy_name` | Optional[str] | Specific policy or None for all |
**Returns:** JSON string with execution history
### 8.2 history_logging(api, exec_types, checkpoint_number, policy_names)
Query execution logs with pagination support.
| Parameter | Type | Description |
|-----------|------|-------------|
| `api` | AirlockAPIWrapper | API wrapper instance |
| `exec_types` | str | JSON list of exec types, e.g., "[3,5,8]" |
| `checkpoint_number` | str | Checkpoint ID for pagination |
| `policy_names` | Optional[str] | Comma-separated policy names or None |
**Returns:** List[Dict] - Execution history records
---
## 9. Error Handling
All API methods may raise:
- `requests.exceptions.RequestException` - Network or HTTP errors
- `ValueError` - Invalid response format
- `KeyError` - Missing expected fields in response
Recommended pattern:
```python
try:
result = api.agent_find_all()
except requests.exceptions.RequestException as e:
logger.error(f"API request failed: {e}")
# Handle error appropriately
```
---
## 10. License
Copyright (C) 2025 James Brotosky, Brandon Wickline
GNU Affero General Public License v3.0
+510
View File
@@ -0,0 +1,510 @@
# Loxide
## User Stories and Use Cases
**Version 1.0 | December 2025**
---
## Epic 1: Multi-Agent Operations
**Epic Statement:** As a administrator, I need to perform bulk operations on multiple endpoints efficiently.
---
### US-1.1: Agent Selection
**User Story:** As a administrator, I want to select multiple agents using various methods so that I can perform bulk operations efficiently.
**Acceptance Criteria:**
- ✅ Can paste a list of device names (700+ lines)
- ✅ Can use wildcards (* and ?) for pattern matching
- ✅ Can import device names from a file
- ✅ Can toggle between exact and fuzzy matching
- ✅ Unmatched entries are clearly displayed
- ✅ Can select/deselect all matched agents
**Priority:** High
**Story Points:** 8
---
### US-1.2: Agent Policy Move
**User Story:** As a administrator, I want to move selected agents to a different policy group so that I can organize endpoints by security requirements.
**Acceptance Criteria:**
- ✅ Can select destination policy from list
- ✅ Move operation provides progress feedback
- ✅ Success/failure results are color-coded
- ✅ Can export results to CSV
**Priority:** High
**Story Points:** 5
---
### US-1.3: Toggle Enforcement Mode
**User Story:** As a administrator, I want to toggle agents between audit and enforcement mode so that I can gradually roll out policy enforcement.
**Acceptance Criteria:**
- ✅ Clear indication of current mode
- ✅ Confirmation before mode change
- ✅ Results displayed after operation
**Priority:** High
**Story Points:** 3
---
### US-1.4: View Agent Execution History
**User Story:** As a administrator, I want to view execution history for selected agents so that I can understand what applications are running.
**Acceptance Criteria:**
- ✅ Date range selector (1-365 days)
- ✅ Results displayed in DataTable
- ✅ Export to CSV functionality
- ✅ Sortable columns
**Priority:** Medium
**Story Points:** 5
---
## Epic 2: Policy Preparation
**Epic Statement:** As a administrator, I need to prepare policies for enforcement by analyzing execution history.
---
### US-2.1: Policy Selection
**User Story:** As a administrator, I want to select source policies for analysis so that I can review their execution history.
**Acceptance Criteria:**
- ✅ Multi-select with checkboxes
- ✅ Filter/search capability
- ✅ Policy hierarchy visible
**Priority:** High
**Story Points:** 5
---
### US-2.2: Configure Analysis Parameters
**User Story:** As a administrator, I want to configure history days and allowlists so that I can customize the analysis scope.
**Acceptance Criteria:**
- ✅ History days configurable from 1 to 365
- ✅ Default value clearly indicated
- ✅ Allowlist selection available
- ✅ Input validation with helpful error messages
**Priority:** High
**Story Points:** 3
---
### US-2.3: Review Analysis Results
**User Story:** As a administrator, I want to review categorized execution history so that I can make informed enforcement decisions.
**Acceptance Criteria:**
- ✅ Results categorized: Approved, Unapproved, Needs Review
- ✅ Hash reputation data displayed
- ✅ Publisher information shown
- ✅ Export to CSV available
- ✅ Color-coded categories
**Priority:** High
**Story Points:** 8
---
### US-2.4: Add Hashes to Allowlist
**User Story:** As a administrator, I want to add approved hashes to an allowlist so that they won't be blocked after enforcement.
**Acceptance Criteria:**
- ✅ Select hashes from analysis results
- ✅ Choose target allowlist
- ✅ Confirmation before adding
- ✅ Success/failure feedback
**Priority:** Medium
**Story Points:** 5
---
## Epic 3: Quiet Agent Detection
**Epic Statement:** As a administrator, I need to identify inactive endpoints ready for enforcement.
---
### US-3.1: Configure Quiet Threshold
**User Story:** As a administrator, I want to set quiet day thresholds so that I can define what constitutes an inactive agent.
**Acceptance Criteria:**
- ✅ Configurable quiet days parameter
- ✅ History days parameter
- ✅ Clear explanation of thresholds
- ✅ Input validation
**Priority:** High
**Story Points:** 3
---
### US-3.2: Run Quiet Agent Analysis
**User Story:** As a administrator, I want the system to analyze agent activity using high-performance Rust code so that I can quickly identify quiet agents.
**Acceptance Criteria:**
- ✅ Progress bar during analysis
- ✅ Console output for Rust progress
- ✅ Non-blocking UI during analysis
- ✅ Clear completion notification
**Priority:** High
**Story Points:** 8
---
### US-3.3: View Quiet Agents
**User Story:** As a administrator, I want to see a list of quiet agents so that I can move them to enforcement.
**Acceptance Criteria:**
- ✅ Results displayed in sortable table
- ✅ Can select agents for bulk move
- ✅ Last activity date shown
- ✅ Export to CSV
**Priority:** High
**Story Points:** 5
---
## Epic 4: OTP Management
**Epic Statement:** As an support technician, I need to manage temporary policy bypasses for end users.
---
### US-4.1: Generate OTP
**User Story:** As an support technician, I want to generate an OTP for an agent so that a user can temporarily bypass policy restrictions.
**Acceptance Criteria:**
- ✅ Select agent by hostname
- ✅ Specify duration in minutes
- ✅ Enter purpose/ticket number
- ✅ OTP code displayed clearly
- ✅ Copy to clipboard functionality
**Priority:** High
**Story Points:** 5
---
### US-4.2: View Active OTPs
**User Story:** As a administrator, I want to view all active OTP sessions so that I can monitor temporary policy bypasses.
**Acceptance Criteria:**
- ✅ List all active OTPs
- ✅ Show hostname, purpose, expiration
- ✅ Filter by status
- ✅ Refresh capability (r key)
- ✅ Sortable columns
**Priority:** High
**Story Points:** 5
---
### US-4.3: Revoke OTP
**User Story:** As a administrator, I want to revoke an active OTP so that I can end a temporary bypass immediately.
**Acceptance Criteria:**
- ✅ Select OTPs for revocation (checkbox)
- ✅ Bulk revoke capability
- ✅ Confirmation before revocation
- ✅ Results displayed after operation
- ✅ Auto-refresh list after revocation
**Priority:** High
**Story Points:** 5
---
### US-4.4: View OTP Activities
**User Story:** As a administrator, I want to see what applications were executed during an OTP session so that I can audit temporary bypasses.
**Acceptance Criteria:**
- ✅ Select OTP to view
- ✅ Display execution list
- ✅ Show file, hash, timestamp
- ✅ Export capability
**Priority:** Medium
**Story Points:** 5
---
## Epic 5: Execution History
**Epic Statement:** As a analyst, I need to investigate execution events on endpoints.
---
### US-5.1: Query Execution History
**User Story:** As a analyst, I want to query execution history for specific agents so that I can investigate security events.
**Acceptance Criteria:**
- ✅ Select agent by hostname
- ✅ Configure date range (start/end)
- ✅ Filter by execution type
- ✅ Results in sortable DataTable
- ✅ Pagination for large results
**Priority:** High
**Story Points:** 8
---
### US-5.2: Export History
**User Story:** As a analyst, I want to export execution history to CSV so that I can perform offline analysis.
**Acceptance Criteria:**
- ✅ Export button available (e key)
- ✅ All visible columns included
- ✅ Proper CSV formatting
- ✅ Timestamp in filename
- ✅ Notification on success
**Priority:** Medium
**Story Points:** 3
---
### US-5.3: Hash Reputation Lookup
**User Story:** As a analyst, I want to see reputation data for executed files so that I can assess risk.
**Acceptance Criteria:**
- ✅ VirusTotal score displayed
- ✅ Publisher information shown
- ✅ Known allowlist membership indicated
- ✅ Risk categorization (approved/unapproved/needs_review)
**Priority:** Medium
**Story Points:** 5
---
## Epic 6: Server Monitoring
**Epic Statement:** As a system administrator, I need to monitor Airlock server activity.
---
### US-6.1: View Server Logs
**User Story:** As a system administrator, I want to view recent server activity so that I can monitor system health.
**Acceptance Criteria:**
- ✅ Default view of last 72 hours
- ✅ Datetime properly formatted (YYYY-MM-DD HH:MM:SS)
- ✅ Refresh via keyboard shortcut (r)
- ✅ Escape to return to main menu
- ✅ Auto-scroll to latest entries
**Priority:** Medium
**Story Points:** 5
---
## Epic 7: Application Configuration
**Epic Statement:** As a power user, I need to customize the application to my preferences.
---
### US-7.1: Theme Selection
**User Story:** As a power user, I want to change the UI theme so that I can work comfortably in different lighting conditions.
**Acceptance Criteria:**
- ✅ Multiple themes available (textual-dark, gruvbox, retro-terminal, amber-terminal)
- ✅ Theme persists across sessions
- ✅ Preview before applying
- ✅ Saved to user config
**Priority:** Low
**Story Points:** 3
---
### US-7.2: Working Directory Access
**User Story:** As a power user, I want to access my working directory from within the application so that I can manage exported files.
**Acceptance Criteria:**
- ✅ Directory tree visible in main menu
- ✅ Keyboard shortcut to open in file manager (f)
- ✅ Configurable working directory
- ✅ Auto-create directory structure
**Priority:** Low
**Story Points:** 3
---
### US-7.3: Credential Management
**User Story:** As a user, I want my API credentials stored securely so that I don't have to enter them every time.
**Acceptance Criteria:**
- ✅ First-time setup prompts for API key
- ✅ Master password protects credentials
- ✅ Password complexity requirements enforced
- ✅ 3 retry attempts on wrong password
- ✅ Platform-native keyring used
**Priority:** High
**Story Points:** 8
---
## Epic 8: Statistics and Reporting
**Epic Statement:** As a administrator, I need visibility into my environment's security posture.
---
### US-8.1: View System Statistics
**User Story:** As a administrator, I want to see an overview of agents and policies so that I can understand my environment.
**Acceptance Criteria:**
- ✅ Total agent count
- ✅ Total policy count
- ✅ Agent status breakdown (Online/Offline/Hidden/Safemode)
- ✅ Visual charts using plotext
- ✅ Configurable time range (1/7/30 days)
**Priority:** Medium
**Story Points:** 5
---
### US-8.2: View Execution Statistics
**User Story:** As a administrator, I want to see execution statistics so that I can identify trends.
**Acceptance Criteria:**
- ✅ Execution counts by type
- ✅ Top executed files
- ✅ Top blocked files
- ✅ Visual bar charts
- ✅ Refresh capability
**Priority:** Medium
**Story Points:** 5
---
## Phase 2: LEMON Integration
**Project:** LEMON (Loxide Execution MONitoring)
**Previous Codename:** Overlock
**Status:** Phase 2 - Separate Project
LEMON user stories are documented separately in `LEMON_03_User_Stories.md`.
**Summary of Phase 2 Loxide Stories:**
- LEMON Sessions Screen - View/create/cancel sessions
- LEMON Hash Review Screen - Approve/reject pending hashes
- Certificate Setup - Configure mTLS authentication
- Audit Chain Verification - Verify log integrity
See LEMON documentation for complete user stories.
---
## Acceptance Test Scenarios
### Scenario: Bulk Agent Move
```gherkin
Given I am on the Multi-Agent Operations screen
When I paste 100 device names into the selector
And I click Search
Then I should see matched agents in the selection list
When I select 50 agents using checkboxes
And I choose "Production Policy" as destination
And I click "Move Selected"
Then I should see a progress indicator
And I should see 50 success results with green indicators
And I should be able to export results to CSV
```
### Scenario: OTP Generation and Revocation
```gherkin
Given I am on the OTP Management screen
When I search for agent "DESKTOP-001"
And I set duration to 60 minutes
And I enter purpose "Ticket #12345 - Software installation"
And I click Generate OTP
Then I should see an 8-character OTP code
And I should be able to copy it to clipboard
Given the OTP is active
When I navigate to OTP Revoke screen
And I select the OTP for "DESKTOP-001"
And I click "Revoke Selected"
Then I should see confirmation dialog
When I confirm revocation
Then I should see success message
And the OTP should no longer appear in active list
```
### Scenario: Policy Preparation Workflow
```gherkin
Given I am on the Policy Prep screen
When I select "Audit Policy A" and "Audit Policy B"
And I set history days to 30
And I click Next
Then I should see execution history being fetched
When the fetch completes
Then I should see categorized results:
| Category | Count |
| Approved | 150 |
| Unapproved | 25 |
| Needs Review | 10 |
When I click "Export to CSV"
Then I should see a file saved notification
And the CSV should contain all execution records
```
---
## License
Copyright (C) 2025 James Brotosky, Brandon Wickline
GNU Affero General Public License v3.0
+1 -1
View File
@@ -34,7 +34,7 @@ import requests
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Current application version - UPDATE THIS ON EACH RELEASE # Current application version - UPDATE THIS ON EACH RELEASE
__version__ = "0.7.0" __version__ = "1.0.0"
# Gitea release API configuration # Gitea release API configuration
GITEA_API_BASE = "https://git.racooncity.org/api/v1" GITEA_API_BASE = "https://git.racooncity.org/api/v1"