Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
72963db
docs: say what the extension actually does, and turn CI green
VKrishna04 Aug 12, 2026
1c6431b
fix(codeforces): stop committing the wrong solve with an empty README
VKrishna04 Aug 12, 2026
34f6395
fix(gamification): stop pointing badges at a Pages site that may not …
VKrishna04 Aug 12, 2026
1f3ce92
fix(site): describe the product that ships, and keep the version honest
VKrishna04 Aug 12, 2026
5200ca1
docs(store): disclose the two host permissions the listings never men…
VKrishna04 Aug 12, 2026
fbf3d6c
chore: release v1.8.0
VKrishna04 Aug 12, 2026
4472479
feat(takeuforward): detect the TUF+ tier instead of asking or assuming
VKrishna04 Aug 12, 2026
043a60d
feat(codeforces): heal imported problems from the problem page
VKrishna04 Aug 12, 2026
fb0465b
fix(github): stop recording a Pages URL GitHub never served
VKrishna04 Aug 12, 2026
d604cec
docs(takeuforward): claim only what the API responses prove
VKrishna04 Aug 12, 2026
edb1b83
fix(roadmap): score milestones against the tags platforms actually store
VKrishna04 Aug 12, 2026
fb08f63
docs(changelog): record the roadmap, Pages and takeuforward changes
VKrishna04 Aug 12, 2026
777e0b5
feat: graph rebuild, AI graph awareness, and library editing batch
VKrishna04 Aug 19, 2026
881f76c
ci: auto-heal formatting drift instead of failing the run
VKrishna04 Aug 19, 2026
c2352a5
ci: extend the auto-heal to version sync and generated manifests
VKrishna04 Aug 19, 2026
0e5b077
fix(github): carry a Pages custom domain into the repo homepage
VKrishna04 Aug 19, 2026
06e2c1c
fix(landing): detect the installed extension reliably, open the library
VKrishna04 Aug 19, 2026
f2e17b3
chore: remove unused code flagged by the CodeQL quality scan
VKrishna04 Aug 19, 2026
b0c3173
fix(graph): wheel zoom, and a user-chosen achievement showcase
VKrishna04 Aug 19, 2026
377c90c
feat(party): podium, duels, leaders and activity — gamified comparison
VKrishna04 Aug 19, 2026
3aa60ed
docs(changelog): fold Unreleased into 1.8.0 for the same-day re-tag
VKrishna04 Aug 19, 2026
755b7a2
feat: fold the UX/gamification batch into 1.8.0
VKrishna04 Aug 19, 2026
dccbc0a
chore: auto-heal formatting and generated-file drift
github-actions[bot] Aug 19, 2026
77fab71
feat: 1.8.1 — next-problem suggestions, vacation auto-detect, hidden-…
VKrishna04 Aug 19, 2026
6f53518
feat(report): knowledge graph section in the Pages report
VKrishna04 Aug 19, 2026
cdf56b2
ci(release): auto-heal fixable drift instead of failing the tag build
VKrishna04 Aug 19, 2026
f338737
fix(security): close the OAuth token-plant paths and partition messag…
VKrishna04 Aug 30, 2026
5672dc1
fix(readme): stop the generated ledger README repeating itself
VKrishna04 Aug 30, 2026
efd6d5c
fix(security): stop a synced or restored setting from redirecting AI …
VKrishna04 Aug 30, 2026
a4c03c3
fix(security): close three paths that let a page write to the user's …
VKrishna04 Aug 30, 2026
ca2e388
fix(security): lock down the OAuth callback page, and pin the net tap…
VKrishna04 Aug 30, 2026
05af7cd
fix(security): stop printing prefixes of tokens and API keys
VKrishna04 Aug 30, 2026
202a933
fix(security): stop index.json from setting the extension's own bookk…
VKrishna04 Aug 30, 2026
148303a
docs(security): publish a threat model, and one security policy inste…
VKrishna04 Aug 30, 2026
ecc0631
feat(roadmap): tell the learner what to study next, and link the topics
VKrishna04 Aug 30, 2026
e9b3817
docs: a SKILL.md so an agent can drive a ledger repository
VKrishna04 Aug 30, 2026
8835ada
fix(ui): restore the space htm drops at a `${…}` line break
VKrishna04 Aug 30, 2026
3cfa668
refactor(ai): make the provider descriptor the whole description of a…
VKrishna04 Aug 30, 2026
e452db4
feat(ai): a provider that answers by asking you
VKrishna04 Aug 30, 2026
6f56463
docs: the two rules an AI handler has to respect
VKrishna04 Aug 30, 2026
378f3d0
docs(store): make every listing claim true of the code, and fix the F…
VKrishna04 Aug 30, 2026
ca4e14d
fix(pages): bake the real counts into the stats page markup
VKrishna04 Aug 30, 2026
f4ca294
fix(pages): finish what the baked counts started
VKrishna04 Aug 31, 2026
3edc470
chore: auto-heal formatting and generated-file drift
github-actions[bot] Aug 31, 2026
e9772b7
build(deps-dev): bump js-yaml from 4.3.1 to 5.4.1
dependabot[bot] Sep 1, 2026
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
15 changes: 13 additions & 2 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,16 @@ npm run build:css # compile Tailwind → src/ui/styles/compiled.css
npm run lint # tsc --noEmit type-check (run before every PR)
```

Load the extension unpacked from `src/` at `chrome://extensions` (Developer mode → Load unpacked).
```bash
npm run build # CSS + dist packaging
```

Load the extension unpacked from `dist/chromium` at `chrome://extensions` (Developer
mode → Load unpacked), or `dist/firefox` at `about:debugging`. Loading `src/` directly
does not work: neither manifest is named `manifest.json` in the source tree — the build
picks one of `manifest-chromium.json` / `manifest-firefox.json` per target and writes it
out. Use `npm run build:fast` as the inner loop; it skips Tailwind, so run
`npm run build:css` after touching a class.

For the Cloudflare Worker:

Expand Down Expand Up @@ -82,7 +91,9 @@ npx wrangler dev # local dev — copy worker/wrangler.toml.example to worker/

## Adding a platform handler

**GeeksForGeeks and Codeforces handlers are the most-wanted contributions right now.** If you know either platform's DOM structure or submission flow, this is a well-scoped, high-impact first PR — the contract is fully documented and LeetCode's handler is the reference implementation.
Five platforms ship today: LeetCode, GeeksForGeeks, Codeforces, NeetCode and
takeuforward. **A sixth platform is the most-wanted contribution right now** — the
contract is fully documented and LeetCode's handler is the reference implementation.

1. Create `src/handlers/platforms/{name}/index.js` extending `BasePlatformHandler`
2. Create `dom-selectors.js` with versioned `SELECTORS`, `LEGACY_SELECTORS`, and `DOMAINS` export
Expand Down
135 changes: 81 additions & 54 deletions .github/SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,70 +1,97 @@
# Security Policy

## Supported Versions
## Reporting a vulnerability

| Version | Supported |
| --------------- | ---------------------- |
| Latest (`main`) | ✅ Actively supported |
| Previous minor | ⚠️ Critical fixes only |
| Older releases | ❌ No longer supported |
**Please do not open a public issue for a security problem.**

We recommend always running the latest release from the [Chrome Web Store](https://chrome.google.com/webstore/detail/codeledger/) or [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/codeledger/).
Two private channels, either is fine:

## Reporting a Vulnerability

**Do not open a public GitHub issue for security vulnerabilities.**

Report privately via email: **github@vkrishna04.me**

Use the subject line: `[CodeLedger Security] <brief description>`
- GitHub's private reporting — **Security → Report a vulnerability** on
[the repository](https://github.com/Life-Experimentalist/Code-Ledger/security).
This is enabled and is the preferred route, because it keeps the report, the
discussion and the eventual advisory in one place.
- Email **github@vkrishna04.me**, subject `[CodeLedger Security] <brief description>`.

### What to include

- **Component**: which part of the extension or worker is affected (e.g., OAuth callback, the GitHub handler, storage)
- **Reproduction steps**: minimal, step-by-step instructions to trigger the issue
- **Impact**: what an attacker could achieve (data exfiltration, token theft, commit injection, etc.)
- **Affected version**: extension version, shown in the extension's settings and in `package.json`
- **Suggested fix** _(optional)_: if you have a patch or a mitigation in mind

### What to expect

| Milestone | Target |
| -------------------------------------------------- | --------------------------------------------------- |
| Initial acknowledgement | Within 72 hours |
| Triage decision (valid / invalid / need more info) | Within 7 days |
| Fix ETA communicated | Within 14 days of confirmed validity |
| Public disclosure | After patch is released (coordinated with reporter) |
- **Component** — which part is affected (the OAuth callback, the GitHub handler,
storage, a platform content script)
- **Reproduction steps** — minimal and step-by-step
- **Impact** — what an attacker actually gets: token theft, commit injection,
data reaching somewhere the user did not choose
- **Version** — shown in the extension's settings and in `package.json`
- **Suggested fix**, optional

We will credit researchers by name (or handle) in the release notes unless they prefer to remain anonymous.
A proof of concept helps and is not required.

## Scope

Security reports are especially relevant for:

- **OAuth and token handling** — GitHub OAuth flow through the Cloudflare Worker; token storage and retrieval paths
- **Secret and API key storage** — AI provider keys, GitHub PATs stored in `chrome.storage.local`
- **Git commit pipeline** — tree API calls, commit integrity, ability to forge commits or modify other repos
- **Worker endpoints** — Cloudflare Worker routes (`/api/auth/*`, `/api/webhook/*`, `/api/admin/*`)
- **Content script isolation** — XSS from problem pages injected into extension UI
- **Supply chain** — the vendored bundles under `src/vendor/` (regenerated from npm by `npm run vendor:preact`), and `mermaid.ink`, the one remote service the UI can call and only after an explicit click
- **Cross-origin message handling** — `postMessage` validation for OAuth callback

## Out of Scope
### What to expect

The following are **not** in scope for the security policy:
This is a one-person project. There is no bounty, and no response time is
guaranteed — treat everything below as intent rather than a commitment.

- Self-XSS (requires the user to paste malicious code into their own browser)
- Denial-of-service against third-party services (LeetCode, GitHub API, Cloudflare)
- Vulnerabilities in the user's own GitHub repository content
- Issues requiring physical access to the user's device
In practice you should hear back within a few days. If a fix is warranted it
ships in the next release, and `docs/CHANGELOG.md` records it under **Security**.
Public disclosure happens after the fix is released, coordinated with you.
Researchers are credited by name or handle in the release notes unless you would
rather stay anonymous.

## Safe Harbor
## Supported versions

CodeLedger welcomes good-faith security research. We will not pursue legal action against researchers who:
The latest release is the supported one. There is no long-term support branch and
no backporting — releases are tags off `main`. Install from
[Releases](https://github.com/Life-Experimentalist/Code-Ledger/releases/latest).

- Act in good faith and give us reasonable time to respond before any public disclosure
- Avoid accessing, modifying, or deleting data that does not belong to them
- Do not disrupt service availability or degrade user experience
- Do not violate user privacy (do not access other users' tokens or data)
## Scope

We treat responsible disclosure as a contribution to the project.
Anything that lets one user's data reach somewhere the user did not choose is in
scope, and so is anything that lets a page CodeLedger runs on read a token or an
API key. Concretely:

- **OAuth and token handling** — the GitHub flow through the Cloudflare Worker,
and every path that stores or reads a token
- **Secret storage** — AI provider keys and GitHub PATs in `chrome.storage.local`
- **The commit pipeline** — Trees API calls, commit integrity, anything that can
forge a commit or reach a repository the user did not name
- **Worker endpoints** — `/api/auth/*`, `/api/webhook/*`, `/api/admin/*`
- **Content script isolation** — a platform page reaching extension state or UI
- **Sync ingest** — `index.json` is repository content and is treated as
untrusted input; anything it can make the extension do is in scope
- **Supply chain** — the vendored bundles under `src/vendor/`, regenerated from
npm by `npm run vendor:preact`, and `mermaid.ink`, the one remote service the
UI can call and only after an explicit click

## Out of scope

- Vulnerabilities in GitHub, in the coding platforms, or in an AI provider —
report those to them
- The contents of a user's own public repository. A public ledger is public on
purpose, and the extension says so before you make one
- The fact that extension storage is not separately encrypted at rest. It is
readable by anyone who already has the OS profile, which is true of the whole
browser profile, and it is documented in [PRIVACY.md](../PRIVACY.md)
- Self-XSS that needs the user to paste hostile code into their own browser
- Denial of service against third-party services
- Anything requiring physical access to the user's device

Known and deliberately unfixed weaknesses are written down in
[docs/THREAT_MODEL.md](../docs/THREAT_MODEL.md) — read it before reporting, so you
do not spend time on something already documented. Finding a way past one of the
limits described there is very much in scope.

## Where the secrets are

No credential belongs in this repository. The OAuth client ID and secret, the
session signing key and the optional webhook and upload tokens are Wrangler
secrets, set with `npx wrangler secret put NAME` from `worker/`, which prompts for
the value rather than taking it as a command-line argument. `worker/wrangler.toml`
is git-ignored for the same reason.

If you believe a secret has been exposed, say so in the report and rotate it
first — rotating is always safe.

## Safe harbour

CodeLedger welcomes good-faith security research, and will not pursue legal action
against researchers who act in good faith, give reasonable time to respond before
public disclosure, avoid accessing or destroying data that is not theirs, and do
not degrade the service for others.
4 changes: 2 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Strong success criteria let you loop independently. Weak criteria ("make it work

## Project Overview

CodeLedger is a **Manifest V3 Chrome/Firefox extension** that automatically commits solved DSA problems (LeetCode, GeeksForGeeks, Codeforces) to a user-owned GitHub repository. No bundler, no transpiler — pure ES6 modules with Preact + htm.
CodeLedger is a **Manifest V3 Chrome/Firefox extension** that automatically commits solved DSA problems (LeetCode, GeeksForGeeks, Codeforces, NeetCode, takeuforward) to a user-owned GitHub repository. No bundler, no transpiler — pure ES6 modules with Preact + htm.

## Stack

Expand Down Expand Up @@ -246,7 +246,7 @@ All handlers live in `src/handlers/` and follow a strict structure:
| Views | PascalCase + View | `ProblemsView.js`, `SettingsView.js` | Distinguish from generic components |
| Core/lib modules | kebab-case | `ai-deduplication.js`, `browser-compat.js` | Lowercase for utility modules |
| Storage keys | CONSTANT_CASE | `CONSTANTS.SK.GITHUB_REPO` | Via `CONSTANTS.SK.*` export only |
| CSS files | kebab-case | `floating-timer.css` | Tailwind input files or compiled |
| CSS files | kebab-case | `theme-variables.css` | Tailwind input files or compiled |
| Data files | kebab-case | `canonical-map.json`, `metadata.json` | In `src/data/` |

### Storage Key Conventions
Expand Down
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ jobs:
name: Lint, format, test
runs-on: ubuntu-latest
timeout-minutes: 15
# The auto-heal step pushes a formatting fix back to the branch on push
# events; fork PRs are downgraded to read automatically.
permissions:
contents: write

steps:
- name: Checkout repository
Expand All @@ -46,7 +50,35 @@ jobs:
- name: Type check
run: npm run lint

# Drift that a script can fix must not block the pipeline. Three of the
# gates below reject exactly that kind of drift: Prettier formatting,
# the version copied from package.json into the manifests and landing
# page, and the host_permissions generated from the handlers' DOMAINS
# exports. On a push, run the fixers themselves and push the result
# back as a bot commit, then carry on gating the healed tree. A
# GITHUB_TOKEN push does not trigger another workflow run, and every
# fixer is idempotent, so this cannot loop. Pull requests keep the
# blocking checks — a fork's token cannot push back. The generators
# run first so anything they write gets formatted; a sync-manifests
# failure (landing-page pattern gone) is real breakage and still fails.
- name: Auto-heal formatting and generated files (push)
if: github.event_name == 'push'
run: |
node dev/sync-manifests.js
node dev/generate-manifest-domains.js
npm run format
if git diff --quiet; then
echo "no drift — nothing to heal"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -am "chore: auto-heal formatting and generated-file drift"
git push origin "HEAD:${{ github.ref_name }}" ||
echo "::warning::branch moved before the heal commit could land — the next push heals it"

- name: Format check
if: github.event_name != 'push'
run: npm run format:check

- name: Unit and worker tests
Expand Down
16 changes: 14 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,20 @@ jobs:
- name: Type check
run: npm run lint

- name: Format check
run: npm run format:check
# Drift that a script can fix must not kill a release — ci.yml heals
# main the same way on push. Heal the checked-out tree in place and
# build the packages from it; the tag stays where it is, and the next
# push to main commits the identical fix there. A sync-manifests
# failure (landing-page pattern gone) is real breakage and still fails.
- name: Auto-heal formatting and generated files
run: |
node dev/sync-manifests.js
node dev/generate-manifest-domains.js
npm run format
if ! git diff --quiet; then
echo "::warning::release tree had fixable drift (healed in place):"
git --no-pager diff --stat
fi

- name: Unit and worker tests
run: npm test
Expand Down
93 changes: 72 additions & 21 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,35 @@

## What gets generated

Only **one file** in the extension is generated from source:

| Generated file | Source input | Tool |
|---|---|---|
| `src/ui/styles/compiled.css` | `src/index.css` + Tailwind class scanning | `@tailwindcss/cli` |

All JavaScript files ship exactly as written — no bundler, no transpiler, no minification of JS.
Eight files in the extension are produced by a build step rather than written by
hand. Everything else ships exactly as it appears in `src/` — no bundler, no
transpiler, no minification of any file CodeLedger itself wrote.

| Generated file | Source input | Tool | Minified |
|---|---|---|---|
| `src/ui/styles/compiled.css` | `src/index.css` + Tailwind class scanning | `@tailwindcss/cli` | yes |
| `src/vendor/preact-bundle.js` | `preact`, `preact/hooks`, `htm` from npm | esbuild | yes |
| `src/vendor/preact.js` | `preact/dist/preact.module.js` | copied verbatim from npm | as published |
| `src/vendor/htm.js` | `htm/dist/htm.module.js` | copied verbatim from npm | as published |
| `src/vendor/vis-network-bundle.js` | `vis-network/standalone` from npm | esbuild | yes |
| `src/vendor/chart-bundle.js` | `chart.js/auto` from npm, via `src/vendor/chart-entry.js` | esbuild | no |
| `src/vendor/chart-source.js` | `chart.js/dist/chart.umd.min.js` | wrapped as a string constant | as published |
| `src/vendor/refresh-badges-source.js` | `src/core/gamification.js` + `src/core/badge-svg.js` | esbuild, wrapped as a string constant | no |

Every one of those files carries a header naming the script that wrote it and
the npm version it came from, so a reviewer can check any single file without
reading this document.

Two of them hold generated code as a **string** rather than as executable code,
because they are written into the user's own GitHub repository rather than run
inside the extension: `chart-source.js` is the chart library the GitHub Pages
report loads, and `refresh-badges-source.js` is a Node script that recomputes
badges in the user's GitHub Actions runner. Neither is evaluated by the
extension.

Nothing is fetched at runtime. The extension's CSP is `script-src 'self'`, and
there is no `eval()` or `new Function()` anywhere in `src/`, `src/vendor/`
included.

---

Expand All @@ -21,37 +43,66 @@ All JavaScript files ship exactly as written — no bundler, no transpiler, no m

Tested on: Windows 11, macOS 14, Ubuntu 22.04.

`esbuild` and `@tailwindcss/cli` are devDependencies — `npm install` is the only
setup step.

---

## Steps to reproduce the extension

```bash
# 1. Install dependencies
npm install

# 2. Compile Tailwind CSS (the only generated file)
npm run build:css
# 1. Install dependencies (exact versions, from package-lock.json)
npm ci

# 2. Regenerate every generated file
npm run build:css # → src/ui/styles/compiled.css
npm run vendor:preact # → src/vendor/preact-bundle.js, preact.js, htm.js
npm run vendor:vis # → src/vendor/vis-network-bundle.js
npm run vendor:chart # → src/vendor/chart-bundle.js
npm run vendor:chart-source # → src/vendor/chart-source.js
npm run vendor:refresh-script # → src/vendor/refresh-badges-source.js

# 3. Assemble the package tree
npm run build:dist # → dist/firefox/ and dist/chromium/
```

After these two commands, `src/ui/styles/compiled.css` will match the file
included in the submitted extension zip exactly.
After step 2, each of those files matches the copy in the submitted zip.

The extension can then be loaded unpacked from the `src/` directory, or
packaged with:
`npm run vendor:refresh-script -- --check` rebuilds without writing and exits
non-zero if the committed bundle differs from its sources. `test/refresh-script.test.js`
runs exactly that, so a stale copy of that one fails the test suite rather than
shipping.

The extension **cannot** be loaded unpacked from `src/`. Neither manifest in
that directory is named `manifest.json` — the build picks
`src/manifest-firefox.json` or `src/manifest-chromium.json` per target and
writes it out as `manifest.json`. Load `dist/firefox` (about:debugging) or
`dist/chromium` (chrome://extensions) instead.

To produce the submitted archive:

```bash
# Produces releases/<version>/codeledger-firefox-v<version>.zip
npm run package:firefox
```

That writes `releases/<version>/codeledger-firefox-v<version>.zip` — along with
the Chrome and source archives, since all three come out of the same publish
step.

---

## Verifying the output

To confirm the compiled CSS matches the extension zip:
To confirm a generated file matches the extension zip:

1. Extract `codeledger-firefox-v<version>.zip`
2. Compare `ui/styles/compiled.css` from the zip against
`src/ui/styles/compiled.css` produced by `npm run build:css`
2. Run the commands above against a clean `npm ci`
3. Compare, for example, `vendor/vis-network-bundle.js` from the zip against
`src/vendor/vis-network-bundle.js`

esbuild output is deterministic for a fixed esbuild version and fixed inputs;
`package-lock.json` pins both the libraries and esbuild itself, which is why
step 1 is `npm ci` rather than `npm install`.

All other files in the zip are copied verbatim from `src/` with no processing.
Every file in the zip that is not in the table above is copied byte-for-byte
from `src/` with no processing.
Loading
Loading