use std::path::PathBuf;
use std::process::{Command, Stdio};
use anyhow::Result;
use zeroize::Zeroize;
use crate::core::store::SecretsStore;
#[derive(Debug, Clone)]
pub struct TokenUsage {
pub file_path: String,
pub line_number: usize,
pub line_content: String,
}
pub fn find_token_usages(token_name: &str, search_dir: &PathBuf) -> Vec<TokenUsage> {
let mut usages = Vec::new();
let patterns = vec![
format!("${{{}}}", token_name), format!("${}", token_name), format!("process.env.{}", token_name), format!("os.environ[\"{}\"]", token_name), format!("os.getenv(\"{}\")", token_name), format!("ENV[\"{}\"]", token_name), token_name.to_string(), ];
if let Ok(entries) = walkdir(search_dir) {
for entry in entries {
if let Ok(content) = std::fs::read_to_string(&entry) {
for (line_num, line) in content.lines().enumerate() {
for pattern in &patterns {
if line.contains(pattern) {
usages.push(TokenUsage {
file_path: entry.to_string_lossy().to_string(),
line_number: line_num + 1,
line_content: line.trim().to_string(),
});
break; }
}
}
}
}
}
usages
}
fn walkdir(dir: &PathBuf) -> Result<Vec<PathBuf>> {
let mut files = Vec::new();
if !dir.is_dir() {
return Ok(files);
}
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let name = path.file_name().unwrap_or_default().to_string_lossy();
if name.starts_with('.') || name == "node_modules" || name == "target" || name == "__pycache__" {
continue;
}
if path.is_dir() {
files.extend(walkdir(&path)?);
} else {
let ext = path.extension().unwrap_or_default().to_string_lossy();
if matches!(ext.as_ref(), "py" | "js" | "ts" | "jsx" | "tsx" | "sh" | "bash" | "env" | "yaml" | "yml" | "json" | "toml" | "rb" | "go" | "rs") {
files.push(path);
}
}
}
Ok(files)
}
pub fn execute_with_secrets(
command: &str,
store: &SecretsStore,
key: &[u8],
) -> Result<std::process::Output> {
let mut env_vars = store.decrypt_all(key)?;
let output = Command::new("sh")
.arg("-c")
.arg(command)
.envs(&env_vars)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()?;
for (_, mut value) in env_vars.drain() {
value.zeroize();
}
Ok(output)
}
pub fn generate_python_wrapper(script_path: &str, locker_path: &PathBuf) -> String {
format!(r#"#!/usr/bin/env python3
"""
Secure wrapper generated by lazy-locker.
This script injects secrets in memory before executing the target script.
"""
import subprocess
import sys
import os
def main():
# Call lazy-locker to get secrets (via secure pipe)
result = subprocess.run(
['lazy-locker', 'export', '--format', 'env'],
capture_output=True,
text=True,
cwd='{locker_dir}'
)
if result.returncode != 0:
print("Error: Unable to load secrets", file=sys.stderr)
sys.exit(1)
# Parse environment variables
env = os.environ.copy()
for line in result.stdout.strip().split('\n'):
if '=' in line:
key, value = line.split('=', 1)
env[key] = value
# Execute target script with injected secrets
subprocess.run([sys.executable, '{script}'] + sys.argv[1:], env=env)
if __name__ == '__main__':
main()
"#,
locker_dir = locker_path.display(),
script = script_path
)
}
pub fn generate_env_reference(store: &SecretsStore, output_path: &PathBuf) -> Result<()> {
let mut content = String::from("# File generated by lazy-locker\n");
content.push_str("# Values are stored securely in the locker.\n");
content.push_str("# Use 'lazy-locker run <command>' to execute with secrets.\n\n");
for secret in store.list_secrets() {
let expiration = secret.expiration_display();
content.push_str(&format!("# {} - {}\n", secret.name, expiration));
content.push_str(&format!("{}=${{LAZY_LOCKER:{}}}\n\n", secret.name, secret.name));
}
std::fs::write(output_path, content)?;
Ok(())
}
pub fn export_env_format(store: &SecretsStore, key: &[u8]) -> Result<String> {
let secrets = store.decrypt_all(key)?;
let mut output = String::new();
for (name, mut value) in secrets {
let escaped_value = value.replace('\\', "\\\\").replace('"', "\\\"");
output.push_str(&format!("{}=\"{}\"\n", name, escaped_value));
value.zeroize();
}
Ok(output)
}
pub fn copy_to_clipboard(value: &str) -> Result<()> {
#[cfg(target_os = "linux")]
{
let result = Command::new("xclip")
.args(["-selection", "clipboard"])
.stdin(Stdio::piped())
.spawn()
.and_then(|mut child| {
use std::io::Write;
if let Some(ref mut stdin) = child.stdin {
stdin.write_all(value.as_bytes())?;
}
child.wait()
});
if result.is_ok() {
return Ok(());
}
let result = Command::new("xsel")
.args(["--clipboard", "--input"])
.stdin(Stdio::piped())
.spawn()
.and_then(|mut child| {
use std::io::Write;
if let Some(ref mut stdin) = child.stdin {
stdin.write_all(value.as_bytes())?;
}
child.wait()
});
if result.is_ok() {
return Ok(());
}
let result = Command::new("wl-copy")
.stdin(Stdio::piped())
.spawn()
.and_then(|mut child| {
use std::io::Write;
if let Some(ref mut stdin) = child.stdin {
stdin.write_all(value.as_bytes())?;
}
child.wait()
});
result.map_err(|_| anyhow::anyhow!("No clipboard tool available (xclip, xsel, wl-copy)"))?;
}
#[cfg(target_os = "macos")]
{
Command::new("pbcopy")
.stdin(Stdio::piped())
.spawn()
.and_then(|mut child| {
use std::io::Write;
if let Some(ref mut stdin) = child.stdin {
stdin.write_all(value.as_bytes())?;
}
child.wait()
})
.map_err(|e| anyhow::anyhow!("pbcopy error: {}", e))?;
}
#[cfg(target_os = "windows")]
{
Command::new("clip")
.stdin(Stdio::piped())
.spawn()
.and_then(|mut child| {
use std::io::Write;
if let Some(ref mut stdin) = child.stdin {
stdin.write_all(value.as_bytes())?;
}
child.wait()
})
.map_err(|e| anyhow::anyhow!("clip error: {}", e))?;
}
Ok(())
}