Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ Bidirectional. Edit anywhere; it converges everywhere. Two sub-workflows, one or

> **Why an orchestrator?** Two independent cron triggers can race each other and interleave commits — W1 mid-run clobbers a `.sync-state.json` update from W2, or vice versa. The orchestrator sequences `pull from Morgen → push merged state out` so the state file mutates serially. If you really want the un-sequenced version (e.g. you're self-hosting and have your own scheduling), pass `SKIP_ORCHESTRATOR=1` to the installer and leave W1/W2's own triggers active.
>
> **Trade-off: W1 is no longer push-instant.** In the default install, you leave W1 inactive so only W0 can fire it — which means an edit to `06-Tasks/` propagates on the next 15-min tick, not on the next `git push`. If you want instant push→Morgen, either (a) activate W1 alongside W0 and accept occasional double-fires (n8n handles duplicate work cheaply because the jsCode is diff-aware), or (b) use `SKIP_ORCHESTRATOR=1` and run bare W1/W2 with their own triggers.
> **Trade-off: W1 is no longer push-instant.** In the default install, you leave W1 inactive so only W0 can fire it — which means an edit to `06-Tasks/` propagates on the next 20-min tick, not on the next `git push`. If you want instant push→Morgen, either (a) activate W1 alongside W0 and accept occasional double-fires (n8n handles duplicate work cheaply because the jsCode is diff-aware), or (b) use `SKIP_ORCHESTRATOR=1` and run bare W1/W2 with their own triggers.
>
> **Known quirk: W0 self-overlap.** n8n's schedule triggers don't skip-if-running. If a 15-min tick lands while the previous W0 is still executing (possible during a large backfill), the second W0 queues up and starts immediately after — which re-introduces the very race W0 was built to prevent. In practice a full W2+W1 cycle finishes in well under 60 seconds, so the overlap window is tiny. If you see it, bump W0 to every 30 min.
> **Known quirk: W0 self-overlap.** n8n's schedule triggers don't skip-if-running. If a 20-min tick lands while the previous W0 is still executing (possible during a large backfill), the second W0 queues up and starts immediately after — which re-introduces the very race W0 was built to prevent. In practice a full W2+W1 cycle finishes in well under 60 seconds, so the overlap window is tiny. If you see it, bump W0 to every 30 min.

### Daemon (local, macOS)

Expand Down Expand Up @@ -266,8 +266,8 @@ Open an issue or a discussion if you try it. Bug reports with `.sync-state.json`
- **macOS only** for the daemon (launchd). Linux / Windows users need to port it.
- **Morgen "inbox" task list only.** Morgen's API doesn't yet expose task-list management, so everything lands in your default inbox list.
- **Morgen task-to-calendar promotion is unavailable** via API. You'll still drag tasks onto the calendar in Morgen's UI (or lean on Morgen's auto-scheduler).
- **Rate budget:** W1 is capped at ~100 Morgen ops per run to stay inside Morgen's 300 points / 15 min. Because W0 fires W2 + W1 serially on every 20-min tick, plan your ops budget against the *cumulative* per-15-min total (W2 + W1 combined) — not W1 in isolation. A fresh backfill that touches 300+ tasks will blow past the Morgen budget; pre-stage via `scripts/morgen-backfill.js` instead.
- **Existing users with a `W2-3-1-Sync-Orchestrator`:** the installer creates a new `W0-Sync-Orchestrator` beside your existing one. Delete the old `W2-3-1-Sync-Orchestrator` in the n8n UI before running `scripts/install-workflows.sh`, or you'll end up with two orchestrators firing on independent 15-min cadences — which defeats the whole serialization guarantee.
- **Rate budget:** W1 is capped at `RATE_BUDGET = 250` Morgen ops per run (well under Morgen's 300 points / 15 min API budget). Because W0 fires W2 + W1 serially on every 20-min tick, plan your ops budget against the *cumulative* per-20-min total (W2 + W1 combined) — not W1 in isolation. A fresh backfill that touches 300+ tasks will blow past the Morgen budget; pre-stage via `scripts/morgen-backfill.js` instead.
- **Existing users with a `W2-3-1-Sync-Orchestrator`:** the installer creates a new `W0-Sync-Orchestrator` beside your existing one. Delete the old `W2-3-1-Sync-Orchestrator` in the n8n UI before running `scripts/install-workflows.sh`, or you'll end up with two orchestrators firing on independent 20-min cadences — which defeats the whole serialization guarantee.

---

Expand Down
11 changes: 5 additions & 6 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ If you discover a security vulnerability, please report it responsibly:
| Credential | Grants | Rotation |
|------------|--------|----------|
| `GITHUB_TOKEN` | Read/write to the private task-mirror repo | GitHub → Settings → Developer settings → Fine-grained PATs |
| `NOTION_TOKEN` | Read/write to the Notion Tasks database you shared with the integration | Notion → My integrations → rotate secret |
| `MORGEN_API_KEY` | Full access to your Morgen calendar + tasks | https://platform.morgen.so/developers-api |
| `N8N_API_KEY` | Full access to your n8n instance (create / edit / activate workflows) | n8n UI → Settings → API → rotate |

Expand All @@ -33,7 +32,7 @@ If you discover a security vulnerability, please report it responsibly:
1. Rotate the credential at its source (links above).
2. Update the value in your local `.env` file.
3. Re-run `./scripts/install-workflows.sh` to push the new credential into n8n.
4. In the n8n UI, deactivate and reactivate W1 / W2 / W3 so their credential references refresh.
4. In the n8n UI, deactivate and reactivate W1 / W2 so their credential references refresh.

## Scope

Expand All @@ -42,16 +41,16 @@ If you discover a security vulnerability, please report it responsibly:
- The local daemon (`src/auto-commit.js` + `daemon/install-daemon.sh`)
- GitHub Actions workflows under `.github/workflows/`

Out of scope: your personal fork of this repo (`YOUR-VAULT-tasks`), your n8n instance, and any third-party services (Notion, Morgen, GitHub) that this project integrates with — please report issues in those systems to the upstream vendor.
Out of scope: your personal fork of this repo (`YOUR-VAULT-tasks`), your n8n instance, and any third-party services (Notion, Morgen, GitHub) that this project integrates with — please report issues in those systems to the upstream vendor. (As of 2026-05-04 the kit no longer integrates with Notion — Notion was dropped from the sync stack; the W3 worker is a no-op stub.)

## Defense in Depth

- **No secrets at rest in the repo.** Workflow JSONs use `{{PLACEHOLDER}}` tokens substituted at install time by `scripts/install-workflows.sh`. The rendered files go to `/tmp/`, are POSTed to n8n, then deleted.
- **CI grep guards.** `.github/workflows/validate.yml` scans workflow JSONs on every push for hardcoded token shapes (`ghp_`, `ntn_`, `sk-`, `ApiKey ...`). A PR that accidentally bakes a secret into a committed JSON will fail CI.
- **Bot-prefix echo guard.** Every automated commit carries a `[bot:daemon]` / `[bot:W1]` / `[bot:W2]` / `[bot:W3]` / `[bot:backfill]` prefix so W1's webhook handler can skip them and avoid an infinite ping-pong.
- **Flip-ratio guard.** W1 and W3 refuse to proceed if a single run would flip >25% (W1) / >30% (W3) of your task state. This catches "I accidentally deleted TASKS-URGENT.md" before it replicates to Notion + Morgen.
- **Bot-prefix echo guard.** Every automated commit carries a `[bot:daemon]` / `[bot:W1]` / `[bot:W2]` / `[bot:backfill]` prefix so W1's webhook handler can skip them and avoid an infinite ping-pong.
- **Flip-ratio guard.** W1 refuses to proceed if a single run would flip >25% of your task state. This catches "I accidentally deleted TASKS-URGENT.md" before it replicates to Morgen.

## Known Limitations

- The daemon shells out to `git` via `execFileSync`. A hostile `$PATH` could swap out `git` for a malicious binary — the installer hardens `PATH` to well-known system locations (`/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin`), but if you install the daemon in an environment with a compromised `$PATH` at that moment, that hardening won't help.
- Morgen's API does not expose webhook notifications — W2 polls on a 60s cron. An attacker who compromises your Morgen account has a ≤60s window before W2 propagates the change to Obsidian + Notion.
- Morgen's API does not expose webhook notifications — W2 runs every 20 min via the W0 orchestrator. An attacker who compromises your Morgen account has a ≤20 min window before W2 propagates the change to Obsidian.