Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when the diff adds or changes a loopback HTTP or WebSocket server, a bind address, a Unix socket, named pipe, XPC or D-Bus service, an Electron or Tauri main-process handler, a custom URL scheme or file association, a privileged helper or updater, or code that spawns tools inside directories a user or download supplied
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
A desktop app's attackers are not "the internet" in general. They are a web page open in the user's browser (which can send requests to 127.0.0.1), another app or OS user on the same machine, a document or archive the user opened, a link the user clicked, and remote content loaded into a webview. Each reaches the app through a different door: a loopback port, a socket, a URL scheme, an IPC channel, a file.
Core principle: name the attacker's starting position, then show the check that stops it at that door. A process name, a path, a port number or a claimed sender field is not authentication — an OS peer credential, a code-signature check, or an unguessable per-launch token is.
Any web page can send requests to http://127.0.0.1:<port>. "Simple" requests — a form POST, text/plain bodies, GET with side effects — need no CORS preflight and still execute; DNS rebinding lets a page on attacker.example read responses once its name resolves to 127.0.0.1; a WebSocket upgrade carries no CORS check at all.
// ❌ a local API that runs agent tasks, open to every page in every browser on the machine
mux.HandleFunc("/api/run", runHandler)
http.ListenAndServe("127.0.0.1:"+port, mux)
// ✅ a per-launch random token in a header (pages cannot read or guess it, and it is never a cookie),
// plus a Host check that defeats DNS rebinding for the routes that need no token
func guard(next http.Handler, token string, port string) http.Handler {
allowed := map[string]bool{"127.0.0.1:" + port: true, "localhost:" + port: true}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !allowed[r.Host] { http.Error(w, "bad host", http.StatusForbidden); return }
if subtle.ConstantTimeCompare([]byte(r.Header.Get("Authorization")), []byte("Bearer "+token)) != 1 {
http.Error(w, "unauthorized", http.StatusUnauthorized); return
}
next.ServeHTTP(w, r)
})
}
Findings: a new local route outside the token middleware that changes state or returns private data; authentication by cookie on a loopback API (CSRF from any page); CORS on the local server reflecting any origin; a WebSocket endpoint that skips the token or the Origin check; the token placed in a URL query string that lands in logs or browser history.
":8080", "0.0.0.0" and "[::]" listen on every interface — the LAN, a café Wi-Fi, a VPN. A local-only service must bind 127.0.0.1 (or ::1). Go's http.ListenAndServe(":"+port, …), Node's server.listen(port) with no host and Python's host="0.0.0.0" all expose it.
/tmp) with a predictable name, or with mode 0777/a permissive umask — another OS user connects or pre-creates the path. Check SO_PEERCRED/getpeereid when the service acts for a specific user.PIPE_REJECT_REMOTE_CLIENTS, or a pipe name another process can create first (squatting).Renderer-side settings are in web-frontend-security §7; the main process is where privilege lives.
// ❌ any frame — including remote content or an injected iframe — can ask the main process to run a command
ipcMain.handle("run", (_e, cmd: string, args: string[]) => execFile(cmd, args));
// ✅ check the sender's origin on every call, and expose an operation, not a primitive
ipcMain.handle("open-project", (e, id: string) => {
if (new URL(e.senderFrame.url).origin !== "app://tasktrooper") throw new Error("forbidden");
return projects.open(validateId(id));
});
Also: shell.openExternal(url) without a scheme allowlist (file:, smb:, custom schemes launch other programs); missing setWindowOpenHandler and will-navigate handlers, so a link navigates the privileged window to remote content; a custom protocol handler (protocol.handle) that serves files by joining a path from the URL (path-traversal-and-file-handling); Tauri commands with broad fs/shell scopes reachable from remote content.
A custom scheme (tasktrooper://), a file association or a share target can be fired by any web page, email or file. Treat everything it carries as untrusted:
state and PKCE verifier of a flow this app started;second-instance or open-url passed on to a child process or parsed for switches — a URL can carry --flag-shaped text; put -- before operands and parse the URL, not the argv string.A directory the user did not author — an extracted archive, a downloaded workspace, a cloned third-party repo — carries configuration that tools execute: .git/config (core.fsmonitor, core.sshCommand, core.hooksPath), .npmrc, .envrc, Makefile, .vscode/tasks.json, a pyproject.toml build backend. Code that runs git status, npm, make or a language server inside such a directory without isolation runs the directory's code. Also: resolving a bare program name through a PATH that includes the working directory.
0644 in a shared location, a world-readable temp file).ps to every local user) — pass them by environment or file descriptor.Any web page reaching command execution, file read or account actions through a loopback server, URL scheme or IPC → CRITICAL. A service exposed to the LAN without authentication → HIGH. Another local user reading credentials or driving a privileged helper → HIGH. A secret on argv → MEDIUM.
127.0.0.1 means "only this app can call it" — browsers can, and so can every local process.contextIsolation: true as protection for an ipcMain handler that executes whatever the renderer sends.listen(":port"), 0.0.0.0 or [::] on a local-only service.ipcMain.handle/ipcMain.on that never reads event.senderFrame, and passes arguments to exec, spawn, fs or shell.openExternal.setAsDefaultProtocolClient or open-url handling that performs an action directly.git, npm, make or a build tool spawned with cwd set to an extracted or downloaded directory.Electron security checklist · Tauri security · OWASP Desktop App Security Top 10 · CWE-214, 306, 346, 427, 668, 1385 · Cloudflare security-audit-skill
아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.