TreeMap
A free, open-source disk-space visualizer. Squarified treemap, duplicate hunting, disk-full forecasting, and cleanup that only ever moves files to the Trash.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗Automatically syncs Canvas LMS assignments to Google Tasks and Canvas events to Google Calendar.
Canvas only offers a read-only calendar feed, so due dates never show up in the tools students actually use. Canvas2Google reads that feed and keeps a dedicated "Canvas" Google Calendar and a "Canvas" Google Tasks list up to date: creating new items, updating changed ones, and never making duplicates.
It runs as a web app (Flask on Vercel, Postgres on Neon, a daily scheduled sync) and as a command-line tool, both built on the same sync engine. I built it as a cybersecurity portfolio project, so the security design is as much the point as the features. See THREAT_MODEL.md.
Live at https://canvas2google.vercel.app. Sign-in is currently limited to an allowlist (single user) while the Google OAuth app awaits verification.
| Preview (dry run) | Settings |
|---|---|
Screenshots use fake demo data from scripts/demo_server.py.
flowchart LR
subgraph Vercel["Vercel (serverless, HTTPS)"]
W["Flask web app<br/>webapp/"]
T["/tasks/sync-all<br/>(cron secret)"]
end
U([Browser]) -->|session cookie + CSRF| W
C([Vercel Cron, daily]) -->|Bearer CRON_SECRET| T
W --> E["Sync engine<br/>canvas_sync/"]
T --> E
L([CLI: cli.py]) --> E
E -->|HTTPS, SSRF-guarded| F[(Canvas iCal feed)]
E -->|OAuth, least-privilege scopes| G[(Google Calendar + Tasks)]
W -->|TLS| D[(Neon Postgres<br/>secrets encrypted)]
canvas_sync/ Shared sync engine (no web code)
feed.py Fetch (HTTPS, size/timeout limits, no redirects) and parse the iCal feed
text.py HTML → plain text; strips script/style and terminal control characters
calendar_sync.py Events → Google Calendar (create / patch / re-link / dry run)
tasks_sync.py Assignments → Google Tasks
state.py Sync records + content fingerprints (versioned, migrated)
config.py Strict feed-URL validation (SSRF allowlist)
webapp/ Flask app: Google sign-in, encryption, database, pages, cron endpoint
cli.py Command-line front end
app.py Vercel entry point
tests/ Test suite (network blocked; fake Google services)
UID maps to its Google event or task ID in a per-user state record.PATCH.UPSERT works the same on SQLite and Postgres, across processes, and expires if a sync crashes.| Area | What's done |
|---|---|
| Least privilege | Google scopes are calendar.app.created (can only touch calendars this app created; verified by a 403 on others) and tasks. Partial consent is detected and refused. |
| Authentication | Google sign-in with state + PKCE, server-side code exchange, verified ID token, and an email allowlist checked before anything is stored. A fresh session starts on sign-in. |
| Secrets at rest | Feed URLs and Google tokens are encrypted with Fernet (AES + HMAC). The key lives outside the database, and each ciphertext is bound to its user row so it can't be moved to another account. |
| Web hardening | CSRF tokens; CSP with no scripts at all; __Host- Secure/HttpOnly/SameSite cookies; HSTS; no framing; no stack traces; auto-escaped templates. |
| SSRF | The server only fetches https://*.instructure.com/feeds/calendars/*.ics, never follows redirects, and rejects metadata IPs, credentials in URLs, odd ports and lookalike domains. |
| Untrusted input | Canvas content is treated as hostile: HTML is parsed, never rendered; control characters are stripped (no terminal escape injection); size and length caps apply. |
| Abuse controls | Database-backed rate limits (sign-in per IP, syncs and Canvas fetches per user, lockout after wrong cron secrets), with IPs stored only as keyed hashes. A user cap, and an invite-only or open sign-up mode. |
| Key rotation | APP_ENCRYPTION_KEY=NEW,OLD (MultiFernet): new data uses the newest key, the daily sync re-encrypts the rest, then the old key is removed. |
| Multi-user isolation | Tests prove one user can't see, change or delete another's data, and no route takes a user ID. |
| Secret hygiene | Nothing secret was ever committed (full-history scans before every push, using a scanner that is self-tested against a known fake). Errors never echo URLs or tokens. OAuth codes are redacted from request logs. |
| Supply chain | Every dependency pinned; Dependabot proposes updates weekly. CI pins GitHub Actions to exact commits and runs with read-only permissions. clean. clean, with three documented suppressions: one false positive, plus two deliberately fake values in the demo script. |
The full analysis (assets, trust boundaries, 30 threats, 14 remaining risks, and incidents caught during development) is in THREAT_MODEL.md.
pip install -r requirements.txt -r requirements-dev.txt
pytest --cov=canvas_sync --cov=webapp
PATCH semantics.state, non-allowlisted accounts, missing scopes), CSRF, encryption at rest, XSS escaping, SSRF inputs, the cron secret, HTTPS cookie flags, time zones across DST, and lock exclusivity across connections.GitHub Actions runs the tests, bandit and pip-audit on every push, using Python 3.12 like production (ci.yml).
Security checks:
pip-audit -r requirements.txt
bandit -r canvas_sync webapp cli.py app.py scripts
You'll need Python 3.12+, a Canvas calendar feed link, and a Google Cloud project with the Calendar and Tasks APIs enabled.
Demo with fake data (no accounts needed):
python scripts/demo_server.py # then open http://127.0.0.1:5050/demo-login
Command line:
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # add CANVAS_FEED_URL; put a Desktop OAuth client in credentials.json
python cli.py # list upcoming items
python cli.py --dry-run # preview changes
python cli.py --sync-events --sync-tasks
Web app locally: fill in the web settings in .env (see .env.example), then:
flask --app webapp run # http://localhost:5000
flask --app "webapp:create_app(start_scheduler=True)" run # with automatic syncing
Deploying to Vercel: import the repo, connect a Neon Postgres database (Production only), and set the environment variables from .env.example plus SECURE_COOKIES=true, OAUTH_REDIRECT_URI and CRON_SECRET. vercel.json schedules the daily sync. The app refuses to start on Vercel without Postgres, rather than silently losing data.
A few things the project taught me, each written up in THREAT_MODEL.md §5:
Referrer-Policy: no-referrer would have broken every form in production, because the CSRF library checks the Referer header over HTTPS.python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"APP_ENCRYPTION_KEY=NEW_KEY,OLD_KEY and redeploy.flask --app webapp rotate-keys), everything is under the new key.APP_ENCRYPTION_KEY=NEW_KEY and redeploy.SIGNUP_MODE to open.Built by Noah (@channel-noah), a cybersecurity student, as a hands-on project in Python, APIs, OAuth and secure web development, working with an AI pair programmer (Claude Code). Not affiliated with Instructure or Google.
pip-auditbandit