Why this verdict
mcp-server . Inconclusive
- inconclusive: gap no_dataflow_for_dynamic_code at jobwatch/score.py
skill skills/jobwatch No issues found
- benign: 20 checks satisfied, no findings for skill:skills/jobwatch
Security findings 0
No individual findings recorded
The inconclusive verdict rests on the open questions and coverage gaps below; 4 candidate findings were refuted on review.
Open questions 2
What the analysis could not settle from the code alone.
[file request jobwatch/cli.py] What host does the `ui` subcommand (cmd_ui and its argparse definition) bind to by default, given service.install passes only --no-browser and --port? Is it localhost/127.0.0.1 only?
Persistence
[file request jobwatch/service.py] What does service.install write (launch agent plist path, command line, arguments)? Does it run only `jobwatch ui` bound to localhost, with a clean uninstall?
Persistence
Refuted findings 4
Candidates the review stage examined and rejected, kept for transparency.
Persistence
criticalRefutedmedium confidenceThe `jobwatch service install` command writes and loads a macOS launchd agent that starts the UI at login and restarts it on exit; this is documented and opt-in, but it is persistence outside the MCP tool surface.
`jobwatch service install` writes ~/Library/LaunchAgents/<LABEL>.plist and loads it
Why it was refuted
The launchd agent is the module's stated purpose, is opt-in via an explicit CLI command, and has a documented uninstall. It is not reachable through any MCP tool or agent instruction.
Persistence
highRefutedlow confidenceThe explicit `jobwatch service install` subcommand installs a login-start, auto-restart service (macOS launch agent) for the local web UI; it is user-invoked and disclosed, with an uninstall path.
jobwatch ui now starts when you log in and restarts if it stops
Why it was refuted
This is an explicit, user-invoked CLI subcommand, not an MCP tool, and its output states what it does and how to undo it. No agent instruction or undeclared concealment leads to it.
Credential access
highRefutedlow confidencesave_application_package reads any local path supplied by the agent and copies its bytes into the store, so a prompt-injected agent could pull in sensitive files such as keys; the tool does declare this, but nothing restricts it to resume or cover-letter files and there is no evidence the data leaves the host.
pkg.attach(path.name, path.read_bytes())
Why it was refuted
Reading local files is the tool's declared purpose: the parameter says it takes local paths and keeps copies. The bytes go into the local store, and no network sink is shown.
Code execution
highRefutedlow confidenceThe undeclared scoring.key_command config value is split and run as an arbitrary subprocess to obtain an API key, so any command placed in the watchlist config executes with the user's privileges (documented, user-configured, no concealment).
r = subprocess.run(argv, capture_output=True, text=True, timeout=KEY_COMMAND_WAIT)
Why it was refuted
scoring.key_command is a documented, user-configured key helper, comparable to a credential helper. The code's docstring describes it, and no agent-reachable path for setting it is shown.
Coverage gaps 9
Parts of the artifact the static analysis could not fully examine.
context:unanalyzable_binary binary content is only byte-scanned
docs/jobwatch-demo.mp4
context:unanalyzable_binary binary content is only byte-scanned
docs/tutorials/jobwatch-01-getting-started.mp4
context:unanalyzable_binary binary content is only byte-scanned
docs/tutorials/jobwatch-02-daily-routine.mp4
context:unanalyzable_binary binary content is only byte-scanned
docs/tutorials/jobwatch-03-where-the-ai-runs.mp4
context:unanalyzable_binary binary content is only byte-scanned
docs/tutorials/jobwatch-04-applying.mp4
context:unanalyzable_binary binary content is only byte-scanned
docs/tutorials/jobwatch-05-tracking.mp4
context:unanalyzable_binary binary content is only byte-scanned
docs/tutorials/jobwatch-06-interview-prep.mp4
context:unanalyzable_binary binary content is only byte-scanned
docs/tutorials/jobwatch-07-comps-and-contacts.mp4
no_dataflow_for_dynamic_code line 230: invoked code is not structurally extracted
jobwatch/score.py