{"id":51152773,"url":"https://github.com/antonefremov/coldstamp","last_synced_at":"2026-06-26T07:30:35.414Z","repository":{"id":364279662,"uuid":"1259344986","full_name":"antonefremov/coldstamp","owner":"antonefremov","description":"Browser extension that warns about checkout dark patterns and keeps your own redacted record of what you agreed to. Local-only. AGPL-3.0.","archived":false,"fork":false,"pushed_at":"2026-06-12T10:55:12.000Z","size":186,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-12T12:17:34.435Z","etag":null,"topics":["chargeback","chrome-extension","consumer-protection","dark-patterns","evidence-collection","privacy","subscriptions","typescript"],"latest_commit_sha":null,"homepage":"https://coldstamp.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/antonefremov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":"THREAT_MODEL.md","audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-04T12:17:33.000Z","updated_at":"2026-06-12T10:54:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/antonefremov/coldstamp","commit_stats":null,"previous_names":["antonefremov/coldstamp"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/antonefremov/coldstamp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antonefremov%2Fcoldstamp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antonefremov%2Fcoldstamp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antonefremov%2Fcoldstamp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antonefremov%2Fcoldstamp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/antonefremov","download_url":"https://codeload.github.com/antonefremov/coldstamp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antonefremov%2Fcoldstamp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34808043,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chargeback","chrome-extension","consumer-protection","dark-patterns","evidence-collection","privacy","subscriptions","typescript"],"created_at":"2026-06-26T07:30:34.475Z","updated_at":"2026-06-26T07:30:35.387Z","avatar_url":"https://github.com/antonefremov.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ColdStamp\n\n[![CI](https://github.com/antonefremov/coldstamp/actions/workflows/ci.yml/badge.svg)](https://github.com/antonefremov/coldstamp/actions/workflows/ci.yml)\n[![Secret scan](https://github.com/antonefremov/coldstamp/actions/workflows/secret-scan.yml/badge.svg)](https://github.com/antonefremov/coldstamp/actions/workflows/secret-scan.yml)\n[![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](./LICENSE)\n\nA Chrome extension that does two complementary jobs at checkout:\n\n1. **Preventative (before you click):** scans the page for dark patterns —\n   pre-ticked recurring add-ons, trial-to-paid conversions, buried recurring\n   prices, mismatched totals, confirmshaming — and shows a small panel.\n2. **Forensic (when you click):** snapshots the consent context, redacts PII,\n   canonicalises, SHA-256 hashes, and stores it locally as a sealed evidence\n   bundle.\n\nEverything runs on-device. Nothing leaves your browser today.\n\n## Reproducible build\n\nThe build is deterministic from a clean checkout — anyone can verify that the\n`dist/` produced from this source matches the `.crx` distributed via the Chrome\nWeb Store, byte-for-byte (excluding signing metadata Chrome attaches at upload).\n\nPrerequisites:\n\n- Node `24.16.0` (pinned via `.nvmrc` and `package.json#engines`)\n- `npm` (bundled with Node)\n\n```bash\nnvm use            # or fnm use — picks up .nvmrc\nnpm ci             # installs the exact tree from package-lock.json\nnpm run build      # outputs dist/\nnpm run verify     # prints a single SHA-256 over the whole dist/ tree\n```\n\n`npm run verify` prints one hex string covering every file in `dist/`. Compare\nit against the SHA-256 published in each GitHub release (added by CI). If they\nmatch, the extension distributed via the Chrome Web Store was built from this\nexact source. If they don't, please open an issue.\n\nTo inspect individual files instead of the rolled-up tree hash:\n\n```bash\nshasum -a 256 dist/manifest.json dist/assets/*.js dist/assets/*.css\n```\n\nUse `npm install` only when intentionally updating dependencies; `npm ci` is\nwhat makes the install deterministic.\n\n## Run the demo\n\n```bash\nnpm ci\nnpm run build       # builds the extension to dist/\nnpm run fixture     # serves the fake checkout at http://localhost:5174\n```\n\n1. Open `chrome://extensions`, enable Developer Mode, **Load unpacked** → `dist/`.\n2. Visit `http://localhost:5174`. The preventative panel should appear in the\n   top-right within ~250ms, listing five findings:\n   - **A recurring charge is pre-ticked** (high)\n   - **The total is more than the items add up to** (high)\n   - **Free now, then a real charge** (medium)\n   - **The recurring price is hidden in the fine print** (medium)\n   - **Guilt-trip decline button** (low)\n3. Fill the form (test data: card `4242 4242 4242 4242`, cvv `123`).\n4. Click **Start free trial**. You should see `[ColdStamp] captured bundle …`\n   in the DevTools console.\n5. Click the extension icon → **Export** a bundle → verify:\n   - card / cvv values are `[REDACTED]`\n   - `networkCharges[0]` shows `amount: 1999`, `recurring: true`, `intervalText: \"month\"`\n   - the raw body is not stored — only `rawBodyDigest`\n\n## Architecture\n\n```\nsrc/\n  manifest.json              MV3\n  background.ts              service worker — viewport screenshot via captureVisibleTab\n  content/\n    page-world.ts            MAIN-world: patches window.fetch + XMLHttpRequest\n    index.ts                 isolated-world orchestrator: detection scan + sealed capture\n    detectors.ts             5 deterministic dark-pattern detectors\n    panel.ts                 Shadow-DOM warning panel (non-blocking)\n  dashboard/\n    index.html, main.ts      popup: list / export / delete\n  lib/\n    types.ts                 EvidenceBundle shape\n    seal.ts                  canonical JSON + SHA-256\n    redact.ts                field-aware PII redaction\n    db.ts                    IndexedDB vault\n    lexicon.ts               word/phrase lists (shared by detection + disclosure capture)\n    dom.ts                   label/price/contrast/visibility helpers\nfixtures/\n  fake-checkout/             triggers all 5 detectors + Stripe-shaped POST\n```\n\n### Design notes\n\n- **Detection and capture are decoupled.** Findings are derived, not sealed.\n  The bundle stays a neutral DOM snapshot so the classifier can be improved\n  later and re-run over stored bundles. This is the project's spec §1\n  principle and a deliberate choice over baking findings into the seal.\n- **Precision over recall on detectors.** A single false positive costs more\n  trust than a missed detection builds. Lexicons are conservative; detectors\n  stop at the first solid hit.\n- **Panel uses Shadow DOM**, not `!important` — clean isolation from site CSS.\n\n## Privacy\n\n- Bundles are stored in IndexedDB inside the extension's origin. Currently\n  unencrypted at rest (planned for a later milestone).\n- The MAIN-world fetch/XHR hook only stores a SHA-256 digest of payment\n  request bodies, never the raw body.\n- No backend, no telemetry, no analytics.\n\n## What's NOT in here yet\n\n- RFC 3161 timestamp anchor (M2)\n- At-rest encryption of the IndexedDB store\n- Onboarding / consent UI / 18+ gate / privacy policy (M4)\n- Encrypted cloud backup (M5)\n- Per-site adapters for known offenders\n- Pre-submit interstitial on `high`-severity findings (kept inline-only for now)\n\n## Continuous integration\n\nEvery pull request and push to `main` runs\n[`/.github/workflows/ci.yml`](./.github/workflows/ci.yml): `npm ci`, type\ncheck, build, and `npm run verify`. The single `dist/` tree digest is printed\nin the run summary so contributors and reviewers can confirm the build is\ndeterministic on Linux too.\n\nEvery tag matching `v*.*.*` runs\n[`/.github/workflows/release.yml`](./.github/workflows/release.yml), which\nbuilds the extension, zips `dist/` into `coldstamp-\u003cversion\u003e.zip`, and\nattaches that zip plus the SHA-256 digests to the GitHub release. The release\nbody is the canonical place to find the verification hash for that version.\n\n[`/.github/workflows/secret-scan.yml`](./.github/workflows/secret-scan.yml)\nruns gitleaks against every PR and push. Contributors can also enable the\nsame scan locally as a pre-commit hook — see [CONTRIBUTING.md](./CONTRIBUTING.md).\n\n## License\n\n[AGPL-3.0-only](./LICENSE). Copyleft on purpose: anyone can read, audit, and\nmodify ColdStamp, but a fork that adds features or changes redaction behaviour\nmust also stay open. This protects the trust contract — the product is \"your\nsymmetric record\"; a closed fork would be the opposite of that.\n\nThe privacy policy at \u003chttps://coldstamp.app/privacy.html\u003e is the canonical\nstatement of what ColdStamp does and does not do with information about you.\n\n## Verifying a release\n\nEach tagged release on GitHub publishes the SHA-256 of `dist/` produced by CI.\nTo check that the Web Store build matches this source:\n\n1. `git checkout v0.X.0` (the tag matching the version in Chrome Web Store)\n2. `npm ci \u0026\u0026 npm run build \u0026\u0026 npm run verify`\n3. Compare the single SHA-256 it prints against the value in that release's\n   notes.\n\nFor a deeper check, also compare per-file hashes:\n\n```bash\nshasum -a 256 dist/manifest.json dist/assets/*.js dist/assets/*.css\n```\n\nA mismatch means the Web Store binary does not correspond to this source.\nOpen an issue immediately.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantonefremov%2Fcoldstamp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fantonefremov%2Fcoldstamp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantonefremov%2Fcoldstamp/lists"}