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