Compare commits
4 Commits
0469f121b1
...
2ad87b4c78
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ad87b4c78 | ||
|
|
82d7f350db | ||
| a4d3442891 | |||
|
|
ad42d40365 |
12
.mcp.json
Normal file
12
.mcp.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"transport": "stdio",
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-github"],
|
||||
"env": {
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1834
2026-hackathon_AgenticFeature.md
Normal file
1834
2026-hackathon_AgenticFeature.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -57,6 +57,9 @@ cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings
|
||||
|
||||
# Rust quick type check (no linking)
|
||||
cargo check --manifest-path src-tauri/Cargo.toml
|
||||
|
||||
# Frontend linting
|
||||
npx eslint . --max-warnings 0
|
||||
```
|
||||
|
||||
### System Prerequisites (Linux/Fedora)
|
||||
@ -116,6 +119,8 @@ All command handlers receive `State<'_, AppState>` as a Tauri-injected parameter
|
||||
|
||||
**Database encryption**: `cfg!(debug_assertions)` → plain SQLite; release → SQLCipher AES-256. Key from `TFTSR_DB_KEY` env var (defaults to a dev placeholder). DB path from `TFTSR_DATA_DIR` or platform data dir.
|
||||
|
||||
**Credential encryption**: API keys stored in `AppSettings` are encrypted using AES-256-GCM via the `aes-gcm` crate. The encryption key is derived from `TFTSR_ENCRYPTION_KEY` env var. Credentials are encrypted on save and decrypted on load. See `commands/system.rs::save_settings()` for implementation.
|
||||
|
||||
### Frontend (React / TypeScript)
|
||||
|
||||
**IPC layer**: All Tauri `invoke()` calls are in `src/lib/tauriCommands.ts`. Every command has a typed wrapper function (e.g., `createIssueCmd`, `chatMessageCmd`). This is the single source of truth for the frontend's API surface.
|
||||
|
||||
@ -325,7 +325,3 @@ Override with the `TFTSR_DATA_DIR` environment variable.
|
||||
| 12 | Release Packaging | ✅ linux/amd64 · linux/arm64 (native) · windows/amd64 |
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Private — internal tooling. All rights reserved.
|
||||
|
||||
@ -126,8 +126,8 @@ pub fn update_server(
|
||||
|
||||
let new_encrypted_env = match &req.env_config {
|
||||
Some(env_json) if !env_json.trim().is_empty() => Some(encrypt_token(env_json)?),
|
||||
Some(_) => None, // Empty string = clear env_config
|
||||
None => existing.env_config.clone(), // No update requested
|
||||
Some(_) => None, // Empty string = clear env_config
|
||||
None => existing.env_config.clone(), // No update requested
|
||||
};
|
||||
|
||||
conn.execute(
|
||||
@ -342,8 +342,9 @@ pub fn get_server_env_config(
|
||||
match encrypted {
|
||||
Some(enc) => {
|
||||
let decrypted = decrypt_token(&enc)?;
|
||||
let parsed: std::collections::HashMap<String, String> = serde_json::from_str(&decrypted)
|
||||
.map_err(|e| format!("Failed to parse env_config JSON: {e}"))?;
|
||||
let parsed: std::collections::HashMap<String, String> =
|
||||
serde_json::from_str(&decrypted)
|
||||
.map_err(|e| format!("Failed to parse env_config JSON: {e}"))?;
|
||||
Ok(Some(parsed))
|
||||
}
|
||||
None => Ok(None),
|
||||
@ -551,8 +552,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let raw = raw.unwrap();
|
||||
assert_ne!(
|
||||
raw,
|
||||
r#"{"API_KEY":"secret123","DEBUG":"1"}"#,
|
||||
raw, r#"{"API_KEY":"secret123","DEBUG":"1"}"#,
|
||||
"env_config should be encrypted at rest"
|
||||
);
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ use tokio::process::Command;
|
||||
|
||||
/// Build a stdio transport from a command path, argument list, and environment variables.
|
||||
/// Rejects relative paths to prevent path traversal.
|
||||
/// Validates environment variable names to block known privilege escalation vectors.
|
||||
pub fn build_stdio_transport(
|
||||
command: &str,
|
||||
args: &[String],
|
||||
@ -16,6 +17,26 @@ pub fn build_stdio_transport(
|
||||
));
|
||||
}
|
||||
|
||||
// Validate env var names to block dangerous variables that could be used for privilege escalation
|
||||
const DANGEROUS_ENV_VARS: &[&str] = &[
|
||||
"LD_PRELOAD",
|
||||
"LD_LIBRARY_PATH",
|
||||
"DYLD_INSERT_LIBRARIES",
|
||||
"DYLD_LIBRARY_PATH",
|
||||
"DYLD_FRAMEWORK_PATH",
|
||||
"DYLD_FALLBACK_LIBRARY_PATH",
|
||||
];
|
||||
|
||||
for key in env.keys() {
|
||||
let upper_key = key.to_uppercase();
|
||||
if DANGEROUS_ENV_VARS.contains(&upper_key.as_str()) {
|
||||
return Err(format!(
|
||||
"Dangerous environment variable '{key}' is not allowed for security reasons. \
|
||||
This variable could be used for privilege escalation attacks."
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut cmd = Command::new(command);
|
||||
cmd.args(args);
|
||||
|
||||
@ -26,3 +47,77 @@ pub fn build_stdio_transport(
|
||||
|
||||
TokioChildProcess::new(cmd).map_err(|e| format!("Failed to spawn stdio process: {e}"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_rejects_relative_path() {
|
||||
let result = build_stdio_transport("./mcp-server", &[], HashMap::new());
|
||||
assert!(result.is_err());
|
||||
if let Err(e) = result {
|
||||
assert!(e.contains("absolute path"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rejects_dangerous_env_vars() {
|
||||
let dangerous_vars = vec![
|
||||
"LD_PRELOAD",
|
||||
"LD_LIBRARY_PATH",
|
||||
"DYLD_INSERT_LIBRARIES",
|
||||
"DYLD_LIBRARY_PATH",
|
||||
"DYLD_FRAMEWORK_PATH",
|
||||
"DYLD_FALLBACK_LIBRARY_PATH",
|
||||
];
|
||||
|
||||
for var in dangerous_vars {
|
||||
let mut env = HashMap::new();
|
||||
env.insert(var.to_string(), "malicious.so".to_string());
|
||||
|
||||
let result = build_stdio_transport("/usr/bin/test", &[], env);
|
||||
assert!(result.is_err(), "Should reject {}", var);
|
||||
if let Err(err) = result {
|
||||
assert!(
|
||||
err.contains("Dangerous environment variable"),
|
||||
"Error should mention dangerous variable: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rejects_dangerous_env_vars_case_insensitive() {
|
||||
let mut env = HashMap::new();
|
||||
env.insert("ld_preload".to_string(), "malicious.so".to_string());
|
||||
|
||||
let result = build_stdio_transport("/usr/bin/test", &[], env);
|
||||
assert!(result.is_err());
|
||||
if let Err(err) = result {
|
||||
assert!(err.contains("Dangerous environment variable"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_allows_safe_env_vars() {
|
||||
let mut env = HashMap::new();
|
||||
env.insert("DEBUG".to_string(), "1".to_string());
|
||||
env.insert("API_KEY".to_string(), "secret123".to_string());
|
||||
env.insert("PATH".to_string(), "/usr/bin".to_string());
|
||||
|
||||
// Note: This will fail to spawn since /usr/bin/test may not exist,
|
||||
// but we're testing that it doesn't reject the env vars
|
||||
let result = build_stdio_transport("/usr/bin/test", &[], env);
|
||||
|
||||
// Should fail with spawn error, not env var validation error
|
||||
if let Err(err) = result {
|
||||
assert!(
|
||||
!err.contains("Dangerous environment variable"),
|
||||
"Should not reject safe env vars, got: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
49
src/lib/INCIDENT_RESPONSE_FRAMEWORK.ts
Normal file
49
src/lib/INCIDENT_RESPONSE_FRAMEWORK.ts
Normal file
@ -0,0 +1,49 @@
|
||||
export const INCIDENT_RESPONSE_FRAMEWORK = `
|
||||
|
||||
---
|
||||
|
||||
## INCIDENT RESPONSE METHODOLOGY
|
||||
|
||||
Follow this structured framework for every triage conversation. Each phase must be completed with evidence before advancing.
|
||||
|
||||
### Phase 1: Detection & Evidence Gathering
|
||||
- **Do NOT propose fixes** until the problem is fully understood
|
||||
- Gather: error messages, timestamps, affected systems, scope of impact, recent changes
|
||||
- Ask: "What changed? When did it start? Who/what is affected? What has been tried?"
|
||||
- Record all evidence with UTC timestamps
|
||||
- Establish a clear problem statement before proceeding
|
||||
|
||||
### Phase 2: Diagnosis & Hypothesis Testing
|
||||
- Apply the scientific method: form hypotheses, test them with evidence
|
||||
- **The 3-Fix Rule**: If you cannot confidently identify the root cause after 3 hypotheses, STOP and reassess your assumptions — you may be looking at the wrong system or the wrong layer
|
||||
- Check the most common causes first (Occam's Razor): DNS, certificates, disk space, permissions, recent deployments
|
||||
- Differentiate between symptoms and causes — treat causes, not symptoms
|
||||
- Use binary search to narrow scope: which component, which layer, which change
|
||||
|
||||
### Phase 3: Root Cause Analysis with 5-Whys
|
||||
- Each "Why" must be backed by evidence, not speculation
|
||||
- If you cannot provide evidence for a "Why", state what investigation is needed to confirm
|
||||
- Look for systemic issues, not just proximate causes
|
||||
- The root cause should explain ALL observed symptoms, not just some
|
||||
- Common root cause categories: configuration drift, capacity exhaustion, dependency failure, race condition, human error in process
|
||||
|
||||
### Phase 4: Resolution & Prevention
|
||||
- **Immediate fix**: What stops the bleeding right now? (rollback, restart, failover)
|
||||
- **Permanent fix**: What prevents recurrence? (code fix, config change, automation)
|
||||
- **Runbook update**: Document the fix for future oncall engineers
|
||||
- Verify the fix resolves ALL symptoms, not just the primary one
|
||||
- Monitor for regression after applying the fix
|
||||
|
||||
### Phase 5: Post-Incident Review
|
||||
- Calculate incident metrics: MTTD (detect), MTTA (acknowledge), MTTR (resolve)
|
||||
- Conduct blameless post-mortem focused on systems and processes
|
||||
- Identify action items with owners and due dates
|
||||
- Categories: monitoring gaps, process improvements, technical debt, training needs
|
||||
- Ask: "What would have prevented this? What would have detected it faster? What would have resolved it faster?"
|
||||
|
||||
### Communication Practices
|
||||
- State your current phase explicitly (e.g., "We are in Phase 2: Diagnosis")
|
||||
- Summarize findings at each phase transition
|
||||
- Flag assumptions clearly: "ASSUMPTION: ..." vs "CONFIRMED: ..."
|
||||
- When advancing the Why level, explicitly state the evidence chain
|
||||
`;
|
||||
@ -562,7 +562,7 @@ export default function MCPServers() {
|
||||
Space-separated KEY=value pairs for non-sensitive values (e.g., DEBUG=1 LOG_LEVEL=info)
|
||||
</p>
|
||||
<Input
|
||||
type="password"
|
||||
type="text"
|
||||
value={form.plaintext_env}
|
||||
onChange={(e) => setForm({ ...form, plaintext_env: e.target.value })}
|
||||
placeholder="KEY1=value1 KEY2=value2"
|
||||
@ -578,11 +578,11 @@ export default function MCPServers() {
|
||||
type="password"
|
||||
value={form.encrypted_env}
|
||||
onChange={(e) => setForm({ ...form, encrypted_env: e.target.value })}
|
||||
placeholder="API_KEY=secret TOKEN=xyz"
|
||||
placeholder={editServer ? "Leave blank to keep existing values" : "API_KEY=secret TOKEN=xyz"}
|
||||
/>
|
||||
{editServer && (
|
||||
<p className="text-xs text-yellow-600 dark:text-yellow-400 mt-1">
|
||||
Leave blank to keep existing encrypted values
|
||||
Encrypted values are stored securely and never displayed. Leave blank to preserve existing values.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user