Repository navigation
Conversation
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 40087217 | Triggered | Generic Encryption Key | ef14c01 | apps/api/src/billing-config.test.ts | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
Trial and lifecycle (docs/wip/plan-limits.md, step 4): - the account's first school starts a 14-day trial; later schools wait for checkout (pending_checkout) and their site is offline - evaluate_school_lifecycle moves schools between active, read_only and offline, and emails the owner once per notice - the admin explains read-only and offline schools with a banner - staff CLI (suspend, unsuspend, import-school, remove-imported) with platform_audit_events - trial-aware plan features, and SSO enforcement kept unless a lapse is confirmed Plan features (step 5): - "Powered by CourseLit" badge under every public page, in the course viewer and learner dashboard sidebars, and on learner email; hidden only with remove_branding, saved from the admin's branding checkbox - during a trial, injected code runs only for the owner, fetched through the owner's session (S25) - SCORM lessons and package changes need the scorm feature - audit log screen and listSchoolAuditEvents; billing events only for the owner in a browser session (S9, S30) - getSchoolPlanFeatures so every team member's screens can explain limits - LearnerLink now renders a real link; the page-builder Link primitive only styles text, so storefront links didn't navigate
Team, product and community invites share a rolling 24-hour budget of 50 per school, and a school can have at most 200 team invitations waiting (docs/wip/plan-limits.md, S6). The admin explains both refusals. OSS installs send their own mail and skip the limits.
Storage (docs/wip/plan-limits.md, S1, S2 and S24): - the upload's school is checked before anything is sealed or deleted, since MediaLit's seal and delete aren't scoped to a group - the declared size is reserved at authorize; the real size is checked at finalize in a transaction that locks the school, and an upload that doesn't fit is discarded - each learner has an upload budget (100 MB by default, up to 1 GB in Settings > Storage); learners together may use 25% of the school's storage, nothing past 90%, and 20 uploads an hour - community uploads need a membership that can post, are always private, must be an image, video, audio file or PDF, and are deleted after a day when no post uses them - the admin and storefront explain refusals; learners never see numbers
- getSchoolBillingUsage returns used and allowed storage and marketing email (from SendLit's team usage); owner-only in a browser session and off MCP (S9) - the plan page shows a meter for each limit; a trial explains that marketing email starts with a paid plan - the owner sees a banner at 80% of a limit, and gets one email: for storage each time usage crosses 80% (again after dropping below 70%), for marketing email once a calendar month on a paid plan - lifecycle emails now link to the admin's plan page (/account/billing)
…domains Abuse handling (docs/wip/plan-limits.md, S12 and S19): - every public storefront page and the certificate page carry "Report this site", on every plan, linking to a fixed CourseLit page - an acceptable use policy and a report-abuse page in the docs, under Policies (reports go to abuse@courselit.app) - reserved subdomains (www, app, api, admin, login, billing, support, docs and more) can't be taken; the address checker shows them as taken
Covers the free trial and plan step, usage meters and 80% warnings, read-only schools, the "Powered by CourseLit" badge and Report this site, trial code injection, SCORM on Business, the audit log and storage settings, learner upload rules, invite limits and reserved addresses.
GitGuardian flagged the literal base64 test key as a hardcoded secret. It was never a real key.
The limit counted upload reservations, which are deleted when an upload finishes, so finished uploads stopped counting. Count authorizations in rate_limit_events instead. Found in browser e2e.
A lesson with no content showed "You are not enrolled in the course." to enrolled learners too. It now says the lesson has no content yet.
Previews fell through to "This lesson type is not available in this viewer yet." for a lesson with no content.
The plan step showed only a name and a price. Each plan now lists what it includes. Limits come from one shared definition that the API enforces.
The scroll container was the centred 640px form column, so on wide screens its scrollbar sat inside the pane. The pane now scrolls.
- plan limits come from PLAN_{PRO,BUSINESS,TRIAL}_MARKETING_EMAILS_MONTH
and _STORAGE (a number, a size like 500MB, or "unlimited"); all six
are required in cloud mode, so a missing one fails startup
- the billing catalog serves them and the plan step builds its plan
lines from them; tests set them in a preload and read them back
- pricing decided 2026-11-15: Pro $29/$290 with 25,000 emails a month
and unlimited storage; Business $79/$790 with 100,000 emails and
unlimited storage; the trial keeps no marketing email and 500 MB
SendLit returns a team's key only when it creates the team. The team id and key are now saved right after creation, so a failed quota update retries with the same team instead of creating it again, which SendLit refuses once the request has changed.
Tests that read the limits without creating a runtime failed when run on their own.
clbotdev
marked this pull request as ready for review
November 15, 2026 13:00
A school's SendLit team is provisioned with the owner's email as Reply-To, next to the school's name as the sender, so replies don't land at the shared sending address. Docs: list the organization key scopes CourseLit needs (teams:manage, teams:keys, usage:read on top of the provisioning ones) and the one-time setup of the school-team organization.
feat(api): send replies to a school's marketing email to its owner
The base branch now names each operation after its contract key, so the plan usage, plan features, audit log and code injection endpoints need their operation ids in the spec.
Pro has 25,000 marketing emails a month and Business 100,000, and both have unlimited storage.
Learner mail on the platform streams now carries the school's name as its From name, at the platform's address; staff mail stays CourseLit's. SendLit uses it once it supports a sender name per message, and older versions ignore it. SMTP installs get the name too.
SendLit refuses them in a From name, so a school name holding one would have its learner mail refused instead of sent under a cleaned-up name.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements
docs/wip/plan-limits.md: Pro and Business plans on courselit.app, a cardless 14-day trial with Business features, plan limits, abuse controls, and the security requirements from the reviews (S1–S31). Every milestone was browser-tested on the local stack.What's in it
Billing and plan state
@codelitdev/billing0.12.0 (planLimits, paid-through entitlement end, store-checked variants).pending_checkout,imported,read_only,suspended) are enforced by a fail-closed allowlist at the dispatch edge.schoolPlan()checks a paid plan first, then a running trial, and returnsunknownwhen billing can't be read.Trial and lifecycle
bun run staff) can suspend, unsuspend and import schools, recorded inplatform_audit_events.Plan features
billing.*events are shown only to the owner in a browser session.getSchoolPlanFeatureslets every team member's screens explain what the plan includes.Limits
getSchoolBillingUsage, owner-only and off MCP).Abuse handling
www,app,login,billing…) can't be taken.Docs:
frontlit/docsis updated for all of the above.Fixes found along the way:
LearnerLinkrendered a span, so storefront links didn't navigate.Testing
Before merging / follow-ups
ef14c018(base64 of "key-key-key…"). The test now builds the key at runtime, but the incident needs marking as a false positive.abuse@courselit.appmailbox must exist.PageBuilder);