diff --git a/.env.example b/.env.example index 948822e5..a55d36d6 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,16 @@ # TipStream Root Environment Variables # Copy this file to .env and fill in real values. +# +# These variables are used by scripts/test-contract.cjs and any future +# deployment automation. The .env file is listed in .gitignore. -# Mnemonic for test scripts (scripts/test-contract.cjs) -# NEVER use a real wallet mnemonic here. -MNEMONIC=your_test_mnemonic_here +# Deployer / sender mnemonic (24-word BIP-39 phrase). +# Keep this in a password manager; paste here only when deploying. +MNEMONIC="" -# Recipient address for test-contract.cjs (required) +# Recipient address for test-contract.cjs (required). # Must be a valid SP... mainnet address and different from the sender. -RECIPIENT=SP_YOUR_TEST_RECIPIENT_ADDRESS +RECIPIENT="" # Tip amount in microSTX (default: 1000, minimum: 1000 = 0.001 STX) AMOUNT=1000 @@ -18,3 +21,9 @@ MESSAGE=On-chain test tip # Set to 1 to build the transaction without broadcasting. # Useful for verifying post-conditions and transaction size. DRY_RUN=0 + +# Network override for the frontend build (mainnet | testnet | devnet). +VITE_NETWORK=mainnet + +# Application URL used for social sharing metadata. +VITE_APP_URL=https://tipstream-silk.vercel.app diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..497fc876 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,16 @@ +# Code owners for TipStream +# These users are automatically requested for review on PRs +# that touch the specified paths. + +# Default owner for all files +* @Mosas2000 + +# Security-sensitive paths require explicit review +.gitignore @Mosas2000 +.gitleaks.toml @Mosas2000 +.env.example @Mosas2000 +settings/ @Mosas2000 +scripts/ @Mosas2000 +SECURITY.md @Mosas2000 +contracts/ @Mosas2000 +.github/workflows/ @Mosas2000 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dd243732 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug Report +about: Report a bug in TipStream +labels: bug +--- + +## Description + + + +## Steps to Reproduce + +1. +2. +3. + +## Expected Behavior + + + +## Actual Behavior + + + +## Environment + +- Browser: +- Wallet: (Leather / Xverse / other) +- Network: (mainnet / testnet / devnet) +- OS: + +## Screenshots + + + +## Additional Context + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..fb380dc1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,21 @@ +--- +name: Feature Request +about: Suggest a new feature or improvement +labels: enhancement +--- + +## Problem + + + +## Proposed Solution + + + +## Alternatives Considered + + + +## Additional Context + + diff --git a/.github/ISSUE_TEMPLATE/security.md b/.github/ISSUE_TEMPLATE/security.md new file mode 100644 index 00000000..f2155d94 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/security.md @@ -0,0 +1,24 @@ +--- +name: Security Vulnerability +about: Report a security issue (prefer private disclosure) +labels: security +--- + +> If this is a sensitive vulnerability, please do **not** open a public +> issue. Follow the private disclosure process in [SECURITY.md](../../SECURITY.md). + +## Description + + + +## Impact + + + +## Reproduction + + + +## Suggested Fix + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..c4bcf07c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,32 @@ +## Summary + + + +## Related Issue + + + +## Changes + + + +- + +## Testing + + + +- [ ] Contract tests pass (`npm test`) +- [ ] Frontend builds without errors (`cd frontend && npm run build`) +- [ ] Linting passes (`cd frontend && npm run lint`) +- [ ] Manual testing (describe below) + +## Deployment Notes + + + +## Security Checklist + +- [ ] No secrets or mnemonics are included in this PR. +- [ ] Post conditions are set to `Deny` on any new contract calls. +- [ ] Environment variables are documented in `.env.example` if added. diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 00000000..e20251b3 --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,33 @@ +name: Secret Scanning +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + gitleaks: + name: Detect leaked secrets + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: gitleaks/gitleaks-action@v2 + with: + config-path: .gitleaks.toml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + verify: + name: Verify credential exclusion + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run secret verification checks + run: ./scripts/verify-no-secrets.sh diff --git a/.gitignore b/.gitignore index 91a62e2c..12aa3e5b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,13 +18,23 @@ npm-debug.log* coverage/ *.info -# Clarinet -**/settings/Mainnet.toml -**/settings/Testnet.toml -settings/Mainnet.toml +# Clarinet session artifacts history.txt costs-reports.json +# Network credentials — mainnet and testnet hold real or funded keys +settings/Mainnet.toml +settings/Testnet.toml +**/settings/Mainnet.toml +**/settings/Testnet.toml + +# Catch any stray key/mnemonic exports +*.pem +*.key +*.p12 +**/secrets/ +*.secret + # OS files .DS_Store Thumbs.db @@ -34,4 +44,7 @@ Thumbs.db .idea/ *.swp *.swo -settings/Testnet.toml \ No newline at end of file + +# Hosting platform local state +.vercel/ +.netlify/ \ No newline at end of file diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 00000000..289868ae --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,52 @@ +# Gitleaks configuration for TipStream +# Detects accidentally committed secrets such as mnemonics or private keys. +# Run locally: gitleaks detect --source . --config .gitleaks.toml +# CI: integrate via GitHub Actions or pre-commit hook. + +title = "TipStream secret scanning rules" + +# BIP-39 mnemonic phrases (12 or 24 words separated by spaces) +[[rules]] +id = "bip39-mnemonic" +description = "Potential BIP-39 mnemonic phrase" +regex = '''(?i)mnemonic\s*=\s*"[a-z ]{20,}"''' +tags = ["key", "mnemonic"] + +[[rules.allowlist]] +paths = [ + '''settings/Devnet\.toml''', + '''settings/.*\.example''', +] + +# Stacks private key (64-hex + 01 suffix) +[[rules]] +id = "stacks-private-key" +description = "Stacks private key" +regex = '''(?i)(secret_key|private_key|senderKey)\s*[:=]\s*["\']?[0-9a-f]{64,66}["\']?''' +tags = ["key", "private-key"] + +[[rules.allowlist]] +paths = [ + '''settings/Devnet\.toml''', +] + +# Generic high-entropy secrets +[[rules]] +id = "generic-secret" +description = "Hardcoded secret or API key assignment" +regex = '''(?i)(api_key|apikey|secret|token|password)\s*[:=]\s*["'][A-Za-z0-9/+=]{16,}["']''' +tags = ["key", "generic"] + +[[rules.allowlist]] +paths = [ + '''\.env\.example''', + '''settings/.*\.example''', +] + +[allowlist] +paths = [ + '''node_modules/''', + '''frontend/dist/''', + '''.cache/''', + '''\.gitleaks\.toml''', +] diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 00000000..74a898db --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,118 @@ +# Architecture + +## Overview + +TipStream is a micro-tipping platform on the Stacks blockchain. Users +connect a Stacks wallet, send STX tips with messages, and view real-time +activity through the React frontend. + +``` +Browser + | + +--> React SPA (Vite) + | | + | +--> @stacks/connect --> Wallet (Leather / Xverse) --> Stacks Node + | | + | +--> @stacks/transactions (read-only) --> Hiro API + | | + | +--> Hiro REST API (events, contract state) + | + +--> Vercel / Netlify (hosting, headers, rewrites) +``` + +## Smart Contract Layer + +The primary contract is `tipstream.clar`, deployed at: + +``` +SP31PKQVQZVZCK3FM3NH67CGD6G1FMR17VQVS2W5T.tipstream +``` + +### Core Responsibilities + +- **Tip storage**: each tip is recorded in the `tips` map with sender, + recipient, amount, fee, message, and block height. +- **Fee collection**: a configurable basis-point fee (default 0.5%) is + deducted from each tip. The minimum fee is 1 microSTX. +- **User profiles**: display name, bio, and avatar stored on-chain. +- **Blocking**: users can block principals to prevent receiving tips. +- **Batch tipping**: partial-failure and strict (atomic) modes for + multi-recipient tips. +- **Admin controls**: fee adjustment, pause/unpause with timelock, + two-step ownership transfer. + +### Extension Contracts (planned) + +| Contract | Purpose | +|---|---| +| tipstream-traits | SIP-010 / SIP-009 trait definitions | +| tipstream-token | TIPS fungible reward token | +| tipstream-escrow | Time-locked escrow tips | +| tipstream-subscription | Recurring patronage payments | +| tipstream-vault | Community treasury | +| tipstream-referral | Referral tracking and incentives | +| tipstream-multisig | Multi-signature admin governance | +| tipstream-rewards | TIPS token reward distribution | +| tipstream-badges | NFT achievement badges | +| tipstream-dao | Token-weighted governance proposals | + +## Frontend Layer + +Built with React 19 and Vite 7. All UI state is managed through React +context (`TipContext`, `ThemeContext`, `DemoContext`). + +### Key Directories + +| Path | Purpose | +|---|---| +| `frontend/src/components/` | UI components (pages and shared UI) | +| `frontend/src/config/` | Contract address and API base URL | +| `frontend/src/context/` | React context providers | +| `frontend/src/hooks/` | Custom hooks (balance, notifications, etc.) | +| `frontend/src/lib/` | Analytics, utilities, web vitals | +| `frontend/src/utils/` | Stacks wallet helper (`stacks.js`) | + +### Routing + +The SPA uses React Router v6. When a user is authenticated the nav +renders Send, Feed, Leaderboard, Activity, and Stats routes. +Unauthenticated visitors see the hero landing page. + +### Wallet Integration + +`frontend/src/utils/stacks.js` wraps `@stacks/connect` to provide +`authenticate()`, `disconnect()`, and `userSession`. The frontend +never handles private keys directly. + +## Hosting + +The frontend is deployed to Vercel. Configuration lives in +`vercel.json` (build command, output directory, headers, rewrites). +A `netlify.toml` is also provided for Netlify as an alternative. + +Both configurations: +- Build the Vite app from `frontend/`. +- Inject `VITE_NETWORK=mainnet` at build time. +- Apply security headers (X-Frame-Options, CSP, etc.). +- Route all paths to `index.html` for client-side routing. + +## Data Flow + +1. **Send tip** — the frontend builds a `contract-call` transaction + with post conditions, the wallet signs it, and the signed + transaction is broadcast to the Stacks node. +2. **Read state** — the frontend queries the Hiro REST API for + read-only contract calls (tip data, stats, profiles). +3. **Events** — the Hiro API `/extended/v1/contract/events` endpoint + provides the tip event feed. + +## Security Boundaries + +| Boundary | Trust Model | +|---|---| +| Wallet <-> Frontend | User controls keys; frontend never sees them | +| Frontend <-> Hiro API | Read-only; no write capability | +| Frontend <-> Stacks Node | Signed transactions only; post conditions enforced | +| Admin <-> Contract | `tx-sender == contract-owner` assertion on every admin call | + +See [SECURITY.md](SECURITY.md) for the full security policy. diff --git a/CHANGELOG.md b/CHANGELOG.md index 71b0942a..77d84922 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Security +- Mainnet seed phrases are excluded from version control (never committed). +- Pre-commit hook blocks staged changes containing mnemonic patterns. +- CI pipeline scans every push and PR for leaked secrets. - Enforce `PostConditionMode.Deny` across all contract interactions (frontend and CLI scripts). Previously the test script and some transaction paths used `PostConditionMode.Allow`, which permits the @@ -22,6 +25,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added +- `SECURITY.md` with vulnerability reporting and wallet rotation advisory. +- `CONTRIBUTING.md` with setup instructions and PR workflow. +- `ARCHITECTURE.md` with system design documentation. +- `settings/Mainnet.toml.example` and `settings/Testnet.toml.example` safe + credential templates. +- `settings/README.md` documenting network configuration setup. +- `.gitleaks.toml` secret scanning configuration. +- `scripts/hooks/pre-commit` mnemonic detection hook. +- `scripts/setup-hooks.sh` one-command hook installer. +- GitHub Actions secret scanning workflow (`.github/workflows/secret-scan.yml`). - Chainhook `/api/admin/events` endpoint for querying admin event history - Chainhook `/api/admin/bypasses` endpoint for querying detected bypass events - Chainhook `bypass-detection.js` module for real-time admin event monitoring @@ -34,7 +47,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - `timelock.js` utility module with countdown calculations and formatting - Admin operations guide ([ADMIN-OPERATIONS.md](docs/ADMIN-OPERATIONS.md)) - Contract upgrade strategy ([CONTRACT-UPGRADE-STRATEGY.md](docs/CONTRACT-UPGRADE-STRATEGY.md)) -- Security policy ([SECURITY.md](SECURITY.md)) - Timelock bypass audit report ([TIMELOCK-BYPASS-AUDIT.md](docs/TIMELOCK-BYPASS-AUDIT.md)) - 40 unit tests for timelock utilities - 18 unit tests for Clarity hex value parser @@ -64,6 +76,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Changed +- `.gitignore` expanded to cover private keys, certificates, and hosting + platform local state. +- `scripts/deploy.sh` validates credential file presence and git tracking + status before deployment. +- `scripts/test-contract.cjs` validates mnemonic word count. +- `settings/Devnet.toml` header updated with cross-reference to example + templates. +- `.env.example` expanded with deployment-specific variables. +- README security section rewritten with credential handling details. - SendTip fee preview now uses dynamic fee percentage from shared constants instead of a hardcoded "0.5%" string. - Test script output now shows full fee breakdown before broadcasting. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..09e6b6fa --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,90 @@ +# Contributing to TipStream + +Thank you for your interest in contributing to TipStream. This guide +covers the setup process, coding standards, and pull request workflow. + +## Getting Started + +1. **Fork the repository** and clone your fork. +2. Install dependencies: + + ```bash + npm install # root — contract tests + cd frontend && npm install # frontend + ``` + +3. Set up local network credentials: + + ```bash + cp settings/Mainnet.toml.example settings/Mainnet.toml # if needed + cp settings/Testnet.toml.example settings/Testnet.toml # if needed + ``` + + Fill in your own mnemonics. **Never commit real credentials.** + +4. Install the pre-commit hook (optional but recommended): + + ```bash + cp scripts/hooks/pre-commit .git/hooks/pre-commit + chmod +x .git/hooks/pre-commit + ``` + +## Development Workflow + +### Smart Contracts + +```bash +clarinet check # syntax and type checking +npm test # run Vitest contract tests on simnet +``` + +### Frontend + +```bash +cd frontend +npm run dev # local dev server at http://localhost:5173 +npm run build # production build +npm run lint # ESLint +``` + +### Running Scripts + +```bash +# Test a mainnet tip (requires funded wallet) +MNEMONIC="..." RECIPIENT="SP..." node scripts/test-contract.cjs +``` + +## Coding Standards + +- **JavaScript/JSX**: follow the ESLint configuration in `frontend/eslint.config.js`. +- **Clarity**: use `clarinet check` with the analysis passes defined in `Clarinet.toml`. +- **Commits**: write clear, descriptive commit messages in imperative mood. + Keep the subject line under 72 characters and add a body when context + is needed. +- **No secrets in code.** Use environment variables or local config + files that are listed in `.gitignore`. + +## Pull Request Process + +1. Create a topic branch from `main`: + + ```bash + git checkout -b fix/short-description + ``` + +2. Make your changes in small, focused commits. +3. Ensure all tests pass (`npm test` at root, `npm run lint` in `frontend`). +4. Push your branch and open a pull request against `main`. +5. Fill in the PR template with a description, related issue number, + and any deployment notes. +6. At least one maintainer review is required before merging. + +## Security + +If you discover a vulnerability, do **not** open a public issue. +Follow the responsible-disclosure process in [SECURITY.md](SECURITY.md). + +## License + +By contributing you agree that your contributions will be licensed +under the MIT License. diff --git a/README.md b/README.md index 4e63f267..4c0f9a7c 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,7 @@ scripts/ lib/ Shared modules (post-conditions) audit-post-conditions.sh CI audit for Allow mode usage deploy.sh Deployment script + hooks/ Git hooks (pre-commit secret scanner) test-contract.cjs Mainnet test tip script docs/ POST-CONDITION-GUIDE.md Post-condition enforcement strategy @@ -194,6 +195,8 @@ deployments/ *.yaml Clarinet deployment plans settings/ *.toml Network configurations + *.toml.example Safe templates (committed) + README.md Credential setup guide ``` ## Security @@ -214,9 +217,24 @@ settings/ - **Frontend enforces timelocked paths**: The AdminDashboard never calls direct bypass functions - **Multisig governance**: Optional multi-signature approval layer for admin actions -The `settings/Devnet.toml` file contains mnemonic phrases and private keys for Clarinet devnet test accounts. These hold no real value and exist only in the local devnet sandbox. Never use devnet mnemonics or keys on mainnet or testnet. +### Credential Handling -See [SECURITY.md](SECURITY.md) for the full security audit and vulnerability reporting guidelines. +- **Mainnet and testnet mnemonics are never committed.** + `settings/Mainnet.toml` and `settings/Testnet.toml` are in `.gitignore`. +- Template files (`Mainnet.toml.example`, `Testnet.toml.example`) are + committed with placeholder values only. +- A pre-commit hook (`scripts/hooks/pre-commit`) scans staged changes for + mnemonic patterns before allowing a commit. +- GitHub Actions runs [gitleaks](https://github.com/gitleaks/gitleaks) + on every push and PR via `.gitleaks.toml`. + +The `settings/Devnet.toml` file contains mnemonic phrases and private keys +for Clarinet devnet test accounts. These hold no real value and exist only +in the local devnet sandbox. Never use devnet mnemonics or keys on mainnet +or testnet. + +See [SECURITY.md](SECURITY.md) for the full security policy, vulnerability +reporting process, and wallet rotation advisory. See [docs/POST-CONDITION-GUIDE.md](docs/POST-CONDITION-GUIDE.md) for the post-condition enforcement strategy. ## Contributing diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 00000000..a68d715f --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,48 @@ +# Roadmap + +Planned milestones for TipStream. Items are subject to change based on +community feedback and funding. + +## Phase 1 — Core Platform (complete) + +- [x] Mainnet deployment of `tipstream.clar` +- [x] Send tip with message +- [x] Batch tipping (partial and strict modes) +- [x] Recursive tipping (tip-a-tip) +- [x] User profiles (name, bio, avatar) +- [x] User blocking +- [x] Admin controls (pause, fee, ownership transfer) +- [x] React frontend with wallet integration +- [x] Live tip feed and leaderboard +- [x] Platform-wide statistics dashboard + +## Phase 2 — Security Hardening (in progress) + +- [x] Credential management framework (example templates, gitignore, hooks) +- [x] Secret scanning CI pipeline (gitleaks) +- [x] SECURITY.md with disclosure process and rotation advisory +- [ ] Post-condition enforcement audit across all scripts +- [ ] Contract upgrade strategy documentation +- [ ] Admin dashboard frontend safeguards + +## Phase 3 — Token Economy + +- [ ] Deploy TIPS fungible reward token (`tipstream-token.clar`) +- [ ] Tip reward distribution contract (`tipstream-rewards.clar`) +- [ ] NFT achievement badges (`tipstream-badges.clar`) +- [ ] Referral tracking and incentives (`tipstream-referral.clar`) + +## Phase 4 — Governance and Treasury + +- [ ] Community vault (`tipstream-vault.clar`) +- [ ] DAO governance proposals (`tipstream-dao.clar`) +- [ ] Multi-signature admin contract (`tipstream-multisig.clar`) +- [ ] Token-weighted voting + +## Phase 5 — Advanced Features + +- [ ] Time-locked escrow tips (`tipstream-escrow.clar`) +- [ ] Recurring subscriptions (`tipstream-subscription.clar`) +- [ ] Cross-platform integrations (Twitter, Discord) +- [ ] Mobile-optimized progressive web app +- [ ] Chainhook event indexing for real-time notifications diff --git a/SECURITY.md b/SECURITY.md index 16a1a8cc..4100db47 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,14 +1,39 @@ # Security Policy -## Reporting Vulnerabilities +## Supported Versions -If you discover a security vulnerability in TipStream, please report it responsibly: +| Version | Supported | +|---|---| +| mainnet (current) | Yes | +| testnet | Best-effort | -1. **Do not** open a public issue -2. Email security concerns to the project maintainers -3. Include a detailed description of the vulnerability -4. Provide steps to reproduce if possible -5. Allow 72 hours for initial response +## Reporting a Vulnerability + +If you discover a security vulnerability in TipStream, please report it +responsibly: + +1. **Do not open a public issue.** +2. Email **security@tipstream.app** (or DM [@Mosas2000](https://github.com/Mosas2000) on GitHub) with: + - A description of the vulnerability. + - Steps to reproduce. + - Potential impact. +3. You will receive an acknowledgment within 48 hours. +4. A fix will be prioritized based on severity and deployed as soon as + practical. + +## Scope + +The following assets are in scope: + +- Smart contracts deployed under `SP31PKQVQZVZCK3FM3NH67CGD6G1FMR17VQVS2W5T`. +- The TipStream frontend hosted at `https://tipstream-silk.vercel.app`. +- Deployment scripts and configuration in this repository. + +Out of scope: + +- Third-party services (Hiro API, wallet extensions). +- Social engineering attacks. +- Denial-of-service attacks against public infrastructure. ## Known Security Considerations @@ -31,6 +56,11 @@ Administrative changes use a 144-block (~24 hour) delay: The frontend AdminDashboard exclusively uses the timelocked functions. Direct bypass functions are reserved for documented emergencies only. +### Missing cancel-pause-change Function + +The contract has `cancel-fee-change` but no corresponding `cancel-pause-change`. If a pause +proposal is submitted, it can only be superseded by a new proposal or waited out. + ### Post Conditions All STX transfers enforce `PostConditionMode.Deny` to prevent transactions from moving @@ -50,12 +80,72 @@ basis points (0.5%). Tips below 1000 microSTX (0.001 STX) are rejected to prevent dust spam. -## Devnet Credentials +## Security Model + +### Smart Contract + +- All admin functions require `tx-sender` to be the contract owner. +- Ownership transfer uses a two-step propose/accept pattern. +- Fee changes are bounded (maximum 10%, i.e. 1000 basis points). +- The `set-paused` function has a timelock: a pause/unpause must be + proposed and can only execute after a delay measured in blocks. +- Post conditions on every state-changing transaction restrict STX + movement to the exact expected amounts. +- Self-tipping is rejected at the contract level. +- Blocked users cannot receive tips from the blocker. + +### Frontend + +- Wallet authentication is handled entirely by `@stacks/connect`. + TipStream never sees or stores private keys. +- All contract calls use `PostConditionMode.Deny` to enforce + explicit post conditions. +- Read-only queries go through the Hiro REST API with no write + capability. + +### Credential Management + +- **Mainnet and testnet mnemonics must never be committed to version + control.** The `.gitignore` excludes `settings/Mainnet.toml` and + `settings/Testnet.toml`. +- Template files (`*.toml.example`) are committed with placeholder + values only. +- The `scripts/test-contract.cjs` script reads its mnemonic from the + `MNEMONIC` environment variable, never from a config file. +- A gitleaks configuration (`.gitleaks.toml`) and a pre-commit hook + (`scripts/hooks/pre-commit`) are provided to catch accidental + secret commits before they reach the remote. +- GitHub Actions runs a secret scan on every push and pull request. + +### Devnet Credentials The `settings/Devnet.toml` file contains mnemonic phrases and private keys for Clarinet devnet test accounts. These are sandbox-only credentials with no real value. Never use devnet mnemonics or keys on mainnet or testnet. +## Wallet Rotation Advisory + +If you believe a mnemonic has been exposed (accidentally committed, +shared in a log, or otherwise leaked): + +1. **Stop using the compromised mnemonic immediately.** +2. Generate a new mnemonic using a trusted BIP-39 tool or hardware + wallet. +3. Transfer all remaining funds from the compromised address to a + new address derived from the fresh mnemonic. +4. If the compromised key is the contract deployer: + - Use `propose-new-owner` to initiate an ownership transfer to + the new address. + - From the new address, call `accept-ownership` to complete the + transfer. +5. Update `settings/Mainnet.toml` (local only) and any CI/CD secrets + with the new mnemonic. +6. Audit recent transactions on the compromised address for + unauthorized activity. +7. If the mnemonic was pushed to a public repository, consider using + `git filter-repo` or BFG Repo-Cleaner to remove the commit from + history, then force-push. All collaborators must re-clone. + ## Dependencies - Frontend dependencies are audited with `npm audit` in CI @@ -70,3 +160,16 @@ devnet mnemonics or keys on mainnet or testnet. - [ ] Use timelocked admin functions in the frontend - [ ] Run `npm audit` before submitting PRs - [ ] Test contract changes on simnet before deployment + +## Incident Response Contacts + +| Role | Contact | +|---|---| +| Maintainer | [@Mosas2000](https://github.com/Mosas2000) | +| Security Reports | security@tipstream.app | + +## Acknowledgments + +We appreciate responsible disclosure. Contributors who report valid +vulnerabilities will be credited in the changelog unless they prefer +anonymity. diff --git a/docs/CONTRACT-UPGRADE-STRATEGY.md b/docs/CONTRACT-UPGRADE-STRATEGY.md index 963d9ffd..0850fb15 100644 --- a/docs/CONTRACT-UPGRADE-STRATEGY.md +++ b/docs/CONTRACT-UPGRADE-STRATEGY.md @@ -1,5 +1,8 @@ # Contract Upgrade Strategy +Clarity smart contracts on Stacks are immutable once deployed. +This document describes how TipStream handles changes to on-chain logic. + ## Current Contract | Field | Value | @@ -9,6 +12,50 @@ | Network | Stacks Mainnet | | Status | Deployed and immutable | +## Immutability Constraint + +Once `tipstream.clar` is deployed to mainnet, its code cannot be modified. +Any bug fix or feature addition requires deploying a new contract. + +## Upgrade Approach + +### 1. Extension Contracts + +New functionality is added through separate contracts (e.g. +`tipstream-escrow.clar`, `tipstream-rewards.clar`) that interact with the +core contract through its public interface. The core contract does not +need to change. + +### 2. Ownership Transfer + +If critical issues require migrating to a replacement core contract: + +1. Deploy the new contract with the fix. +2. Use `propose-new-owner` on the old contract to transfer admin rights + to a migration principal (or burn address) so the old contract can + be cleanly retired. +3. Update the frontend `config/contracts.js` to point to the new contract + address. + +### 3. Admin Controls + +The existing contract has intentionally limited admin surface: + +| Function | Purpose | Risk Mitigation | +|---|---|---| +| `set-paused` | Emergency pause | Timelock delay before execution | +| `set-fee-basis-points` | Adjust fee rate | Maximum cap of 1000 (10%) | +| `propose-new-owner` / `accept-ownership` | Transfer ownership | Two-step confirmation | + +### 4. Frontend Version Gating + +When a contract is retired, the frontend should: + +- Stop routing transactions to the old contract. +- Display a migration banner directing users to the new contract. +- Continue reading historical data from the old contract for activity + feeds. + ## Known Issues in v1 ### 1. Timelock Bypass (High Severity) @@ -105,7 +152,34 @@ Allow the community to request a timelock extension on pending changes: ) ``` -### Migration Strategy +## Timelock Details + +The `set-paused` function uses a pending-pause mechanism: + +1. Admin calls `set-paused(true)` which records the intent and computes + an effective block height (`block-height + timelock-delay`). +2. After the delay has elapsed, admin calls `execute-pause-change` to + apply the state change. +3. This prevents an attacker who gains admin access from instantly + pausing the contract to block tips. + +## Emergency Procedures + +If the deployer mnemonic is compromised: + +1. Immediately call `propose-new-owner` to transfer ownership to a + secure address. +2. From the new address, call `accept-ownership`. +3. Rotate the compromised mnemonic (see SECURITY.md for details). + +If a contract vulnerability is discovered: + +1. Use `set-paused` to halt operations (subject to timelock). +2. Communicate the issue through the security disclosure channel. +3. Deploy a patched contract and update the frontend. +4. Retire the old contract by transferring ownership to a burn address. + +## Migration Strategy 1. Deploy `tipstream-v2` with the improved admin functions 2. Set `tipstream-v1` to paused state via the timelocked path diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..8bf98749 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,16 @@ +# Docs + +Supplementary documentation for TipStream. + +| Document | Purpose | +|---|---| +| [CONTRACT-UPGRADE-STRATEGY.md](CONTRACT-UPGRADE-STRATEGY.md) | How to handle changes to immutable Clarity contracts | + +See also the root-level documentation: + +- [README.md](../README.md) — project overview +- [ARCHITECTURE.md](../ARCHITECTURE.md) — system design +- [SECURITY.md](../SECURITY.md) — security policy and disclosure +- [CONTRIBUTING.md](../CONTRIBUTING.md) — contributor guide +- [CHANGELOG.md](../CHANGELOG.md) — change history +- [ROADMAP.md](../ROADMAP.md) — development plan diff --git a/scripts/README.md b/scripts/README.md index 0d22d5fc..f390cd4b 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -51,6 +51,12 @@ timestamps the deployment. bash scripts/deploy.sh ``` +## Hooks + +| Hook | Purpose | +|---|---| +| `hooks/pre-commit` | Block commits containing mnemonic phrases | + ## setup-hooks.sh Install the git pre-commit hook that runs secret scanning before each @@ -68,3 +74,12 @@ the pre-commit hook and CI secret-scan workflow. ```bash bash scripts/verify-no-secrets.sh ``` + +## Security Notes + +- `deploy.sh` validates that `settings/Mainnet.toml` exists and is not + tracked by git before proceeding. +- `test-contract.cjs` reads the mnemonic from the `MNEMONIC` environment + variable. Never pass mnemonics as command-line arguments (they appear + in shell history and process listings). +- Run `verify-no-secrets.sh` at any time to audit the repository state. diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 32aaa5f8..d5cb9398 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -4,10 +4,17 @@ set -euo pipefail # TipStream Deployment Script # Deploys the 10-contract ecosystem to Stacks mainnet # Total cost: ~1 STX (0.1 STX per contract × 10 contracts) +# +# Prerequisites: +# - clarinet CLI installed +# - settings/Mainnet.toml populated with deployer mnemonic +# (copy settings/Mainnet.toml.example if it does not exist) echo "Starting TipStream ecosystem deployment..." echo "" +# ---- Pre-flight checks ---- + # Verify clarinet is installed if ! command -v clarinet &> /dev/null; then echo "Error: clarinet is not installed or not in PATH." @@ -15,6 +22,29 @@ if ! command -v clarinet &> /dev/null; then exit 1 fi +# Verify mainnet settings exist (we never ship the real file) +if [ ! -f "settings/Mainnet.toml" ]; then + echo "Error: settings/Mainnet.toml not found." + echo "Copy the template and fill in your mnemonic:" + echo " cp settings/Mainnet.toml.example settings/Mainnet.toml" + exit 1 +fi + +# Warn if the mainnet config still contains placeholder text +if grep -q '' settings/Mainnet.toml 2>/dev/null; then + echo "Error: settings/Mainnet.toml still contains the placeholder mnemonic." + echo "Replace it with your real deployer mnemonic before deploying." + exit 1 +fi + +# Safety net: abort if the credentials file is tracked by git +if git ls-files --error-unmatch settings/Mainnet.toml &>/dev/null; then + echo "CRITICAL: settings/Mainnet.toml is tracked by git." + echo "Run: git rm --cached settings/Mainnet.toml" + echo "Then commit. Deployment aborted to prevent mnemonic exposure." + exit 1 +fi + # Run contract checks before deploying echo "Running contract syntax check..." if ! clarinet check; then @@ -58,6 +88,14 @@ echo "" echo "Applying deployment plan..." clarinet deployments apply -p deployments/default.mainnet-plan.yaml +DEPLOY_EXIT=$? +if [ $DEPLOY_EXIT -ne 0 ]; then + echo "" + echo "Deployment failed with exit code $DEPLOY_EXIT." + echo "Check the output above for details." + exit $DEPLOY_EXIT +fi + echo "" echo "=== Post-Deployment Setup ===" echo "Run these transactions after deployment:" @@ -66,4 +104,4 @@ echo " Authorizes the rewards contract to mint TIPS tokens" echo " 2. tipstream-vault.add-authorized()" echo " Authorizes the DAO to withdraw from the vault" echo "" -echo "Deployment complete." +echo "Deployment complete at $(date -u '+%Y-%m-%d %H:%M:%S UTC')." diff --git a/scripts/hooks/pre-commit b/scripts/hooks/pre-commit new file mode 100755 index 00000000..002c4dd2 --- /dev/null +++ b/scripts/hooks/pre-commit @@ -0,0 +1,37 @@ +#!/bin/bash +# pre-commit hook: scan staged files for potential secrets +# Install by copying to .git/hooks/pre-commit and making it executable: +# cp scripts/hooks/pre-commit .git/hooks/pre-commit +# chmod +x .git/hooks/pre-commit + +set -euo pipefail + +RED='\033[0;31m' +NC='\033[0m' + +# Pattern matches 12+ lowercase words separated by single spaces +# (the shape of a BIP-39 mnemonic phrase). +MNEMONIC_RE='mnemonic\s*=\s*"[a-z]+ [a-z]+ [a-z]+ [a-z]+ [a-z]+ [a-z]+ [a-z]+ [a-z]+ [a-z]+ [a-z]+ [a-z]+ [a-z]+' + +# Check staged files only (skip binary files) +MATCHES=$(git diff --cached --name-only -z | xargs -0 git diff --cached -U0 -- 2>/dev/null | grep -iE "$MNEMONIC_RE" || true) + +if [ -n "$MATCHES" ]; then + echo -e "${RED}BLOCKED: staged changes appear to contain a mnemonic phrase.${NC}" + echo "" + echo "Matched content:" + echo "$MATCHES" + echo "" + echo "If this is intentional (e.g. a devnet config), bypass with:" + echo " git commit --no-verify" + exit 1 +fi + +# If gitleaks is available, run a fast staged scan +if command -v gitleaks &> /dev/null; then + gitleaks protect --staged --config .gitleaks.toml --no-banner 2>/dev/null || { + echo -e "${RED}BLOCKED: gitleaks found potential secrets in staged files.${NC}" + echo "Review the output above and remove secrets before committing." + exit 1 + } +fi diff --git a/scripts/setup-hooks.sh b/scripts/setup-hooks.sh new file mode 100755 index 00000000..7ba3ad8d --- /dev/null +++ b/scripts/setup-hooks.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -euo pipefail + +# Install project git hooks. +# Run once after cloning: ./scripts/setup-hooks.sh + +HOOK_SRC="scripts/hooks/pre-commit" +HOOK_DST=".git/hooks/pre-commit" + +if [ ! -d ".git" ]; then + echo "Error: run this script from the repository root." + exit 1 +fi + +if [ ! -f "$HOOK_SRC" ]; then + echo "Error: $HOOK_SRC not found." + exit 1 +fi + +cp "$HOOK_SRC" "$HOOK_DST" +chmod +x "$HOOK_DST" +echo "Installed pre-commit hook at $HOOK_DST" diff --git a/scripts/test-contract.cjs b/scripts/test-contract.cjs index b96b5537..08f998a1 100644 --- a/scripts/test-contract.cjs +++ b/scripts/test-contract.cjs @@ -148,6 +148,7 @@ async function runTestTip() { // Sanitize the error output to ensure mnemonics and private keys // are never leaked to logs or CI output. let safeMessage = (error.message || String(error)); + safeMessage = safeMessage.replace(/[0-9a-f]{64}/gi, '[REDACTED_KEY]'); if (mnemonic) { safeMessage = safeMessage.replace(new RegExp(mnemonic.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '[REDACTED]'); } diff --git a/scripts/verify-no-secrets.sh b/scripts/verify-no-secrets.sh new file mode 100755 index 00000000..27061dfe --- /dev/null +++ b/scripts/verify-no-secrets.sh @@ -0,0 +1,77 @@ +#!/bin/bash +set -euo pipefail + +# Verify that the repository does not contain leaked secrets. +# Intended for local use and CI pipelines. +# +# Usage: ./scripts/verify-no-secrets.sh + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +ERRORS=0 + +echo "=== TipStream Secret Verification ===" +echo "" + +# 1. Mainnet.toml must not be tracked +if git ls-files --error-unmatch settings/Mainnet.toml &>/dev/null; then + echo -e "${RED}FAIL: settings/Mainnet.toml is tracked by git${NC}" + ERRORS=$((ERRORS + 1)) +else + echo -e "${GREEN}PASS: settings/Mainnet.toml is not tracked${NC}" +fi + +# 2. Testnet.toml must not be tracked +if git ls-files --error-unmatch settings/Testnet.toml &>/dev/null; then + echo -e "${RED}FAIL: settings/Testnet.toml is tracked by git${NC}" + ERRORS=$((ERRORS + 1)) +else + echo -e "${GREEN}PASS: settings/Testnet.toml is not tracked${NC}" +fi + +# 3. No .env file tracked +if git ls-files --error-unmatch .env &>/dev/null; then + echo -e "${RED}FAIL: .env is tracked by git${NC}" + ERRORS=$((ERRORS + 1)) +else + echo -e "${GREEN}PASS: .env is not tracked${NC}" +fi + +# 4. Scan tracked files for mnemonic-like patterns +MNEMONIC_HITS=$(git grep -l 'mnemonic\s*=\s*"[a-z]\+ [a-z]\+ [a-z]\+' -- ':!settings/Devnet.toml' ':!*.example' ':!*.md' ':!scripts/hooks/*' ':!.gitleaks.toml' 2>/dev/null || true) +if [ -n "$MNEMONIC_HITS" ]; then + echo -e "${RED}FAIL: mnemonic patterns found in tracked files:${NC}" + echo "$MNEMONIC_HITS" + ERRORS=$((ERRORS + 1)) +else + echo -e "${GREEN}PASS: no mnemonic patterns in tracked files${NC}" +fi + +# 5. Scan tracked files for private key patterns (64-hex) +KEY_HITS=$(git grep -lE '(secret_key|private_key)\s*[:=]\s*[0-9a-f]{64}' -- ':!settings/Devnet.toml' ':!*.example' ':!*.md' ':!.gitleaks.toml' 2>/dev/null || true) +if [ -n "$KEY_HITS" ]; then + echo -e "${RED}FAIL: private key patterns found in tracked files:${NC}" + echo "$KEY_HITS" + ERRORS=$((ERRORS + 1)) +else + echo -e "${GREEN}PASS: no private key patterns in tracked files${NC}" +fi + +# 6. Check that .gitignore entries exist +for PATTERN in "settings/Mainnet.toml" "settings/Testnet.toml" ".env"; do + if ! grep -qF "$PATTERN" .gitignore 2>/dev/null; then + echo -e "${RED}FAIL: $PATTERN is missing from .gitignore${NC}" + ERRORS=$((ERRORS + 1)) + fi +done +echo -e "${GREEN}PASS: .gitignore contains expected credential patterns${NC}" + +echo "" +if [ $ERRORS -gt 0 ]; then + echo -e "${RED}$ERRORS issue(s) found. Fix them before pushing.${NC}" + exit 1 +else + echo -e "${GREEN}All checks passed.${NC}" +fi diff --git a/settings/Devnet.toml b/settings/Devnet.toml index 6c2c0e5b..f815e2c7 100644 --- a/settings/Devnet.toml +++ b/settings/Devnet.toml @@ -2,6 +2,9 @@ # WARNING: These are standard Clarinet devnet test accounts. # They hold NO real value and exist only in the local devnet sandbox. # NEVER use these mnemonics or keys on mainnet or testnet. +# +# If you need mainnet or testnet credentials, copy the corresponding +# .example file in this directory and fill in your own mnemonic. # See: https://docs.hiro.so/clarinet/guides/clarinet-deploy # ====================================================================== diff --git a/settings/Mainnet.toml.example b/settings/Mainnet.toml.example new file mode 100644 index 00000000..375cb5bc --- /dev/null +++ b/settings/Mainnet.toml.example @@ -0,0 +1,23 @@ +# TipStream Mainnet Configuration +# Copy this file to Mainnet.toml and fill in your credentials. +# +# SECURITY: Mainnet.toml is listed in .gitignore and must NEVER be committed. +# Each wallet mnemonic controls real STX funds on the Stacks mainnet. +# Store mnemonics in a password manager or hardware wallet backup, +# not in plaintext files shared across machines. + +[network] +name = "mainnet" +stacks_node_rpc_address = "https://api.hiro.so" +deployment_fee_rate = 1 + +[accounts.deployer] +mnemonic = "" +# stx_address: + +# Add additional wallets below as needed. +# Each entry must have a unique name and its own mnemonic. +# +# [accounts.wallet_1] +# mnemonic = "" +# stx_address: diff --git a/settings/README.md b/settings/README.md new file mode 100644 index 00000000..15f18065 --- /dev/null +++ b/settings/README.md @@ -0,0 +1,43 @@ +# Network Settings + +This directory contains Clarinet network configuration files that control +how contracts are deployed and which accounts are used for each environment. + +## Files + +| File | Purpose | Committed | +|---|---|---| +| `Devnet.toml` | Local development sandbox accounts | Yes (test-only keys) | +| `Testnet.toml` | Stacks testnet deployment credentials | No | +| `Mainnet.toml` | Stacks mainnet deployment credentials | No | +| `Mainnet.toml.example` | Template for mainnet config | Yes | +| `Testnet.toml.example` | Template for testnet config | Yes | + +## Setup + +1. Copy the relevant example file: + + ```bash + cp settings/Mainnet.toml.example settings/Mainnet.toml + ``` + +2. Open the new file and replace placeholder mnemonics with your own. +3. Verify the file is ignored before committing anything: + + ```bash + git status settings/ + ``` + + `Mainnet.toml` and `Testnet.toml` should never appear as tracked files. + +## Security Rules + +- **Never commit real mnemonics.** The `.gitignore` already excludes + `Mainnet.toml` and `Testnet.toml`, but always double-check before pushing. +- **Devnet keys are safe to commit.** They are standard Clarinet sandbox + accounts with no real-world value. +- **Rotate immediately** if a mnemonic is accidentally pushed. See + `SECURITY.md` in the project root for the incident-response process. +- **Store mnemonics** in a password manager (1Password, Bitwarden, etc.) + or use a hardware-wallet backup. Do not store them in plaintext + on shared drives or cloud storage. diff --git a/settings/Testnet.toml.example b/settings/Testnet.toml.example new file mode 100644 index 00000000..a1d042de --- /dev/null +++ b/settings/Testnet.toml.example @@ -0,0 +1,18 @@ +# TipStream Testnet Configuration +# Copy this file to Testnet.toml and fill in your testnet mnemonic. +# +# Testnet STX can be obtained from the Stacks testnet faucet: +# https://explorer.hiro.so/sandbox/faucet?chain=testnet +# +# SECURITY: Testnet.toml is listed in .gitignore and must NEVER +# be committed. Even testnet keys should be treated carefully +# to avoid accidental reuse on mainnet. + +[network] +name = "testnet" +stacks_node_rpc_address = "https://api.testnet.hiro.so" +deployment_fee_rate = 10 + +[accounts.deployer] +mnemonic = "" +# stx_address: