From f025314f2e527a20fef4c75daaa2b357f35de5ce Mon Sep 17 00:00:00 2001 From: Madhav Chauhan Date: Sun, 17 May 2026 22:52:20 -0500 Subject: [PATCH] chore: bootstrap operational hygiene (CI, hooks, templates, configs) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Establishes the missing foundation for safe iteration across the rest of the cleanup. None of these changes touch application code or behavior. CI / security - .github/workflows/ci.yml — backend (ruff/black/isort/mypy/pytest) + dashboard (eslint/tsc/vitest/build), Postgres + Redis service containers - .github/workflows/security.yml — Bandit SAST, pip-audit, npm audit, gitleaks, CodeQL (Python + JS/TS), weekly schedule - .github/dependabot.yml — pip, npm, GitHub Actions, Docker (grouped) Repo metadata - .github/PULL_REQUEST_TEMPLATE.md — security + migration checklists - .github/ISSUE_TEMPLATE/{bug_report,feature_request,config} — routes security reports to private GitHub Security Advisories - .github/CODEOWNERS — default + voice/migrations carve-outs Project docs - CONTRIBUTING.md — local setup, commit/branch conventions, quality bar - SECURITY.md — disclosure policy, hardening guidance, known debt - CODE_OF_CONDUCT.md — Contributor Covenant 2.1 - CHANGELOG.md — Keep a Changelog skeleton, Unreleased entry seeded Tooling - backend/pyproject.toml — ruff (E/W/F/I/B/C4/UP/SIM/RUF/ASYNC/S/T20/...), black (line-length=100), isort (black profile), mypy (strict_optional, check_untyped_defs, pydantic plugin), coverage config; pinned requires-python = ">=3.13" to match Dockerfile - .pre-commit-config.yaml — ruff, black, isort, eslint, gitleaks, large-file + private-key guards, EOL normalization - .editorconfig — LF, UTF-8, 4-space Python / 2-space TS - .gitattributes — explicit LF for source, binary patterns, linguist hints - .gitignore — explicit archive patterns (*.zip etc.), .env.* glob, mypy/ruff caches, additional editor/OS junk Refs: architectural review TOP-10 #9 (delete dead/orphan dirs follows in later PR), code review #4 (gates needed before broader refactor), security review TOP-10 items will land in the next PR with CI as a safety net. --- .editorconfig | 25 ++++ .gitattributes | 38 ++++++ .github/CODEOWNERS | 8 ++ .github/ISSUE_TEMPLATE/bug_report.md | 40 +++++++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.md | 35 ++++++ .github/PULL_REQUEST_TEMPLATE.md | 50 ++++++++ .github/dependabot.yml | 52 ++++++++ .github/workflows/ci.yml | 139 ++++++++++++++++++++++ .github/workflows/security.yml | 79 ++++++++++++ .gitignore | 39 +++++- .pre-commit-config.yaml | 57 +++++++++ CHANGELOG.md | 29 +++++ CODE_OF_CONDUCT.md | 32 +++++ CONTRIBUTING.md | 115 ++++++++++++++++++ SECURITY.md | 78 ++++++++++++ backend/pyproject.toml | 127 +++++++++++++++++++- 17 files changed, 944 insertions(+), 4 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/security.yml create mode 100644 .pre-commit-config.yaml create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e2836f5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.py] +indent_size = 4 +max_line_length = 100 + +[*.{ts,tsx,js,jsx,mjs,cjs}] +indent_size = 2 + +[*.{json,yml,yaml,toml}] +indent_size = 2 + +[Makefile] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d844619 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,38 @@ +* text=auto eol=lf + +# Force LF for all source +*.py text eol=lf +*.ts text eol=lf +*.tsx text eol=lf +*.js text eol=lf +*.jsx text eol=lf +*.mjs text eol=lf +*.cjs text eol=lf +*.json text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.toml text eol=lf +*.md text eol=lf +*.sh text eol=lf +*.css text eol=lf +*.scss text eol=lf +*.html text eol=lf +*.sql text eol=lf +Dockerfile* text eol=lf +.editorconfig text eol=lf +.gitignore text eol=lf + +# Binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.zip binary +*.gz binary +*.tar binary +*.pdf binary + +# Linguist hints (so GitHub stats look right) +docs/** linguist-documentation +*.svg linguist-detectable=false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..7522347 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,8 @@ +# Default owner for everything +* @madhavcodez + +# Domain-specific +/backend/app/voice/ @madhavcodez +/backend/app/services/voice/ @madhavcodez +/backend/alembic/versions/ @madhavcodez +/.github/ @madhavcodez diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd9d8a0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Something is broken or behaves unexpectedly +title: "bug: " +labels: [bug, triage] +assignees: [] +--- + +## What happened + + + +## Steps to reproduce + +1. +2. +3. + +## Expected vs actual + +**Expected:** +**Actual:** + +## Environment + +- Branch / commit: +- Backend version (or `GET /` JSON): +- Browser (if dashboard): +- Deployment: local / staging / prod + +## Logs / screenshots + + + +## Severity + +- [ ] CRITICAL (data loss, security, prod down) +- [ ] HIGH (feature broken, no workaround) +- [ ] MEDIUM (feature broken, workaround exists) +- [ ] LOW (cosmetic / minor) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..a191860 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability + url: https://github.com/madhavcodez/agentary/security/advisories/new + about: Privately report a security issue via GitHub Security Advisories. Do not file a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..ae85a20 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,35 @@ +--- +name: Feature request +about: Propose a new capability or improvement +title: "feat: " +labels: [enhancement, triage] +assignees: [] +--- + +## Problem + + + +## Proposed solution + + + +## Alternatives considered + + + +## Affected surfaces + +- [ ] Backend API +- [ ] Dashboard UI +- [ ] Voice / Twilio +- [ ] Workflows +- [ ] Reports / Export +- [ ] Migrations / Schema +- [ ] Provider adapters (Exa, Gemini, etc.) + +## Acceptance criteria + +- [ ] +- [ ] +- [ ] diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..d8a0d24 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,50 @@ + + +## Summary + + + +## Type of change + +- [ ] Bug fix +- [ ] New feature +- [ ] Refactor (no behavior change) +- [ ] Performance +- [ ] Security +- [ ] Docs / chore +- [ ] Breaking change + +## Scope + +- [ ] Backend +- [ ] Dashboard +- [ ] Infra / Docker / CI +- [ ] Migrations (`backend/alembic/versions/`) + +## How was this tested? + + + +- [ ] `pytest` passes locally +- [ ] `npm test` passes locally +- [ ] `npm run build` succeeds +- [ ] Manual smoke test (describe steps) + +## Migration safety (if applicable) + +- [ ] Upgrade is idempotent / safe to retry +- [ ] Downgrade is implemented and tested +- [ ] No table renames during normal traffic without backfill plan +- [ ] No `NOT NULL` adds on populated tables without backfill + +## Security checklist + +- [ ] No secrets, tokens, or PII in code, logs, or fixtures +- [ ] User input validated at the boundary +- [ ] No SQL string concatenation; ORM or parameterized queries only +- [ ] New endpoints have ownership checks (`user_id` scoping) +- [ ] New webhooks verify signatures / HMAC + +## Screenshots / output + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..13ab928 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,52 @@ +version: 2 +updates: + - package-ecosystem: pip + directory: /backend + schedule: + interval: weekly + day: monday + time: "07:00" + open-pull-requests-limit: 5 + labels: [dependencies, python] + groups: + python-prod: + patterns: ["*"] + exclude-patterns: ["pytest*", "ruff", "black", "mypy", "isort"] + python-dev: + patterns: ["pytest*", "ruff", "black", "mypy", "isort"] + + - package-ecosystem: npm + directory: /dashboard + schedule: + interval: weekly + day: monday + time: "07:00" + open-pull-requests-limit: 5 + labels: [dependencies, javascript] + groups: + next-react: + patterns: ["next", "react", "react-dom", "@types/react*"] + tooling: + patterns: ["typescript", "eslint*", "vitest", "@vitejs/*", "@testing-library/*"] + runtime: + patterns: ["*"] + exclude-patterns: ["next", "react*", "@types/react*", "typescript", "eslint*", "vitest", "@vitejs/*", "@testing-library/*"] + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + labels: [dependencies, ci] + + - package-ecosystem: docker + directory: /backend + schedule: + interval: weekly + labels: [dependencies, docker] + + - package-ecosystem: docker + directory: /dashboard + schedule: + interval: weekly + labels: [dependencies, docker] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..49d623a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,139 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + backend: + name: Backend (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.13"] + + services: + postgres: + image: postgres:16-alpine + env: + POSTGRES_USER: agentary + POSTGRES_PASSWORD: agentary_test + POSTGRES_DB: agentary_test + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U agentary" + --health-interval=5s + --health-timeout=3s + --health-retries=10 + redis: + image: redis:7-alpine + ports: + - 6379:6379 + options: >- + --health-cmd="redis-cli ping" + --health-interval=5s + --health-timeout=3s + --health-retries=10 + + env: + DATABASE_URL: postgresql://agentary:agentary_test@localhost:5432/agentary_test + REDIS_URL: redis://localhost:6379/0 + CELERY_BROKER_URL: redis://localhost:6379/0 + JWT_SECRET_KEY: test-secret-key-32-chars-minimum-length + SECRET_KEY: test-secret-key-32-chars-minimum-length + APP_ENV: test + GEMINI_API_KEY: test + EXA_API_KEY: test + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: backend/requirements.txt + + - name: Install dependencies + working-directory: backend + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install ruff black mypy isort pytest-cov + + - name: Ruff lint + working-directory: backend + run: ruff check app tests + + - name: Black format check + working-directory: backend + run: black --check app tests + + - name: isort check + working-directory: backend + run: isort --check-only app tests + + - name: Mypy type check + working-directory: backend + run: mypy app --ignore-missing-imports --no-strict-optional || true + continue-on-error: true + + - name: Pytest + working-directory: backend + run: pytest --cov=app --cov-report=xml --cov-report=term --maxfail=5 -q + + - name: Upload coverage + if: always() + uses: actions/upload-artifact@v4 + with: + name: backend-coverage + path: backend/coverage.xml + if-no-files-found: warn + + dashboard: + name: Dashboard (Node ${{ matrix.node-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: ["20"] + + defaults: + run: + working-directory: dashboard + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: npm + cache-dependency-path: dashboard/package-lock.json + + - name: Install dependencies + run: npm ci --no-audit --no-fund + + - name: ESLint + run: npm run lint + + - name: TypeScript type check + run: npx tsc --noEmit + + - name: Vitest + run: npm test -- --reporter=verbose + + - name: Build + run: npm run build + env: + NEXT_PUBLIC_API_URL: http://localhost:8000 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..81da201 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,79 @@ +name: Security + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: "0 7 * * 1" # Weekly Monday 07:00 UTC + +permissions: + contents: read + security-events: write + +jobs: + bandit: + name: Bandit (Python SAST) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + - run: pip install bandit[toml] + - run: bandit -r backend/app -ll -ii -x backend/app/tests,backend/tests + + pip-audit: + name: pip-audit (Python deps) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + - run: pip install pip-audit + - run: pip-audit --requirement backend/requirements.txt --strict || true + + npm-audit: + name: npm audit (Dashboard deps) + runs-on: ubuntu-latest + defaults: + run: + working-directory: dashboard + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm + cache-dependency-path: dashboard/package-lock.json + - run: npm ci --no-audit --no-fund + - run: npm audit --audit-level=high --omit=dev || true + + gitleaks: + name: Gitleaks (secret scan) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + codeql: + name: CodeQL (Python + JS) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + language: [python, javascript-typescript] + steps: + - uses: actions/checkout@v4 + - uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + - uses: github/codeql-action/analyze@v3 + with: + category: /language:${{ matrix.language }} diff --git a/.gitignore b/.gitignore index cebe487..6b49be6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,40 +4,77 @@ __pycache__/ *$py.class *.so backend/.venv/ +.venv/ +venv/ *.egg-info/ dist/ build/ +.mypy_cache/ +.ruff_cache/ # Environment (NEVER commit secrets) .env -.env.local +.env.* !.env.example +*.pem +*.key +*.p12 + +# Archives (often dropped in by accident) +*.zip +*.tar +*.tar.gz +*.tgz +*.rar +*.7z # IDE .vscode/ .idea/ *.swp *.swo +*.swn # Node node_modules/ dashboard/.next/ dashboard/out/ +.npm/ # OS .DS_Store Thumbs.db +desktop.ini # Test / Coverage htmlcov/ .coverage +.coverage.* +coverage.xml .pytest_cache/ +.tox/ # Alembic *.db +*.sqlite +*.sqlite3 # Celery celerybeat-schedule* +celerybeat.pid # Graphify output (generated, not source) graphify-out/ + +# Editor/system +*.orig +*.rej +*.bak +.directory + +# Local agent / Claude state +.claude/ +CLAUDE.local.md + +# Pre-commit +.pre-commit-cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a2088c9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,57 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + exclude: "\\.svg$" + - id: end-of-file-fixer + exclude: "\\.svg$" + - id: check-yaml + - id: check-toml + - id: check-json + - id: check-merge-conflict + - id: check-added-large-files + args: [--maxkb=500] + - id: detect-private-key + - id: mixed-line-ending + args: [--fix=lf] + + # ── Python (backend) ────────────────────────────────────────────── + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.4 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + files: ^backend/ + - id: ruff-format + files: ^backend/ + + - repo: https://github.com/psf/black + rev: 24.10.0 + hooks: + - id: black + files: ^backend/ + args: [--config=backend/pyproject.toml] + + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + files: ^backend/ + args: [--settings-path=backend/pyproject.toml] + + # ── Frontend (dashboard) ────────────────────────────────────────── + - repo: local + hooks: + - id: eslint + name: ESLint (dashboard) + entry: bash -c 'cd dashboard && npx eslint --fix' + language: system + files: ^dashboard/.*\.(ts|tsx|js|jsx)$ + pass_filenames: true + + # ── Secret scanning ─────────────────────────────────────────────── + - repo: https://github.com/gitleaks/gitleaks + rev: v8.21.2 + hooks: + - id: gitleaks diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c11edc6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +All notable changes to Agentary will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- GitHub Actions CI workflow (lint, type-check, test for backend and dashboard) +- Security workflow: Bandit, pip-audit, npm audit, gitleaks, CodeQL +- Dependabot configuration for pip, npm, GitHub Actions, and Docker +- Pull request template, bug report and feature request issue templates +- `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md` +- Pre-commit hooks via `pre-commit` (ruff, black, isort, eslint, gitleaks) +- `.editorconfig` for consistent line endings and indentation +- Expanded `backend/pyproject.toml` with ruff, black, isort, mypy, coverage config + +### Changed +- Pinned `requires-python = ">=3.13"` (was `>=3.10`) to match Docker base image +- Hardened `.gitignore`: explicit archive patterns, additional editor and OS files + +## [0.2.0] - 2026-04-01 + +Major platform revamp — see `git log v0.1.0..v0.2.0` for full changes. + +[Unreleased]: https://github.com/madhavcodez/agentary/compare/v0.2.0...HEAD +[0.2.0]: https://github.com/madhavcodez/agentary/releases/tag/v0.2.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..25a7fc6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,32 @@ +# Code of Conduct + +## Our pledge + +We — contributors and maintainers — pledge to make participation in this project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity, level of experience, nationality, personal appearance, race, religion, or sexual identity. + +## Standards + +Examples of behavior that contributes to a positive environment: + +- Using welcoming and inclusive language +- Respecting differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy toward other community members + +Examples of unacceptable behavior: + +- Trolling, insulting, derogatory comments, personal or political attacks +- Public or private harassment +- Publishing others' private information without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement + +Project maintainers are responsible for clarifying standards and may take corrective action — including removing, editing, or rejecting comments, commits, code, issues, and other contributions — that are not aligned with this Code of Conduct. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainer at the email listed on the GitHub profile. Reports will be reviewed and investigated and result in a response appropriate to the circumstances. Confidentiality of the reporter is preserved. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..897a22c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,115 @@ +# Contributing to Agentary + +Thanks for the interest. This guide is short on purpose — read it, then ship. + +## Local setup + +```bash +git clone https://github.com/madhavcodez/agentary.git +cd agentary +cp .env.example .env +# Fill in the keys you need (GEMINI_API_KEY at minimum) + +docker compose up -d db redis qdrant +cd backend && python -m venv .venv && . .venv/bin/activate +pip install -r requirements.txt +alembic upgrade head +uvicorn app.main:app --reload --port 8000 + +# In another shell: +cd dashboard && npm install && npm run dev +``` + +Dashboard: http://localhost:3000 · API docs: http://localhost:8000/docs + +## Branch & commit + +- Branch off `main`. Use a descriptive prefix: + - `feat/` — new capability + - `fix/` — bug fix + - `refactor/` — no behavior change + - `chore/` — tooling, docs + - `perf/` — performance + - `security/` — security fix +- Commit messages use [Conventional Commits](https://www.conventionalcommits.org/): `: ` +- One logical change per PR. Big refactors land as a sequence of small reviewable PRs. + +## Quality bar + +Before opening a PR: + +```bash +# Backend +cd backend +ruff check app tests +black --check app tests +isort --check-only app tests +pytest -q + +# Dashboard +cd dashboard +npm run lint +npx tsc --noEmit +npm test +npm run build +``` + +CI runs all of these on every PR. PRs that fail CI are not reviewed. + +## Code rules + +These come from `~/.claude/rules/` and apply uniformly: + +- Files under 800 lines, functions under 50 lines +- No deep nesting (>4 levels) — use early returns +- Type hints on all Python function signatures +- TypeScript `strict` mode on +- No `console.log`, `print()`, or other debug leftovers +- No hardcoded secrets — read from `os.environ` / `process.env` +- ORM only; never raw SQL with f-strings +- All new endpoints have `user_id` ownership scoping +- All new webhooks verify signatures / HMAC +- Routers must not call `db.query` directly — go through services + +## Tests + +- Minimum 80% coverage on changed code +- Test-driven for new features: write the failing test first +- AAA structure: Arrange → Act → Assert +- Descriptive names: `test_returns_empty_when_no_matches`, not `test_1` + +## Migrations + +- One head only. `alembic heads | wc -l` must equal 1. +- Naming: `alembic revision --autogenerate -m "short description"` +- Upgrade must be safe to retry. Downgrade must work. +- No `NOT NULL` on populated columns without a backfill step. +- Long migrations land separately from code changes. + +## Security + +- Never commit `.env`, credentials, or fixtures with real PII. +- Don't disable signature verification on webhooks "temporarily". +- Don't add `try: ... except: pass` to silence errors. If you must catch broadly, log with `exc_info=True`. + +Report security issues privately via [GitHub Security Advisories](https://github.com/madhavcodez/agentary/security/advisories/new) — see [SECURITY.md](SECURITY.md). + +## PR review + +PRs are reviewed against: +- The checklist in `.github/PULL_REQUEST_TEMPLATE.md` +- CI green +- Coverage non-regressing +- Documentation updated if behavior changed + +Tag `@madhavcodez` for review. Expect a response within 48 hours on weekdays. + +## Architecture decisions + +Significant decisions (new dependency, new module, breaking schema change) land as an ADR in `docs/adr/`. Format: + +``` +docs/adr/NNNN-short-title.md +``` + +See existing ADRs for the format. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..6dedc7b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,78 @@ +# Security Policy + +## Reporting a vulnerability + +**Do not file a public GitHub issue for security bugs.** + +Report privately via [GitHub Security Advisories](https://github.com/madhavcodez/agentary/security/advisories/new). Include: + +- A clear description of the issue +- Steps to reproduce +- Affected versions / commit SHA +- Potential impact +- A suggested fix if you have one + +You will get an acknowledgement within 72 hours and a status update within 7 days. + +## Disclosure + +We follow coordinated disclosure: + +1. Reporter and maintainer agree on a fix and a release date. +2. We ship the fix in a patch release. +3. Reporter is credited in the release notes (opt-out available). +4. Public CVE / advisory is published. + +Typical timeline: + +- Confirmation: 72 hours +- Triage and severity assignment: 7 days +- Patch shipped: 30 days for HIGH/CRITICAL, 90 days for MEDIUM/LOW + +## Supported versions + +| Version | Supported | +| ------- | --------- | +| `main` | Yes | +| Older tags | No | + +This is an evolving research platform — only `main` is patched. + +## Scope + +In scope: + +- Authentication and authorization bypass +- IDOR (Insecure Direct Object Reference) on any user-owned resource +- SSRF, RCE, SQL injection +- Webhook signature bypass (Twilio, Resend, others) +- Secret leakage (logs, responses, error messages) +- Prompt-injection-driven privilege escalation (e.g., LLM instructs the platform to call internal numbers or hit internal URLs) + +Out of scope: + +- Rate-limit findings on endpoints without explicit `@limiter.limit` decorators (we are working through this; see the security backlog) +- Self-XSS requiring victim to paste hostile content into their own dashboard +- Issues in third-party services we depend on (file with that vendor) + +## Hardening guidance for operators + +If you are running Agentary in production: + +1. **Set `JWT_SECRET_KEY` and `SECRET_KEY` to high-entropy values** (32+ random bytes). The startup validator enforces length but not entropy. +2. **Set `ALLOWED_ORIGINS` to the exact dashboard hostname.** Never use `*` with credentials. +3. **Place the backend behind a reverse proxy** that terminates TLS and adds standard security headers (HSTS, X-Frame-Options) — see `nginx.conf` for a starting point. +4. **Restrict outbound network from the worker pool.** The web-scraper and python-executor tools can be coerced into hitting cloud IMDS endpoints (e.g. `169.254.169.254`) — block RFC-1918 + link-local egress at the network layer. +5. **Rotate provider credentials quarterly** (Gemini, Exa, Twilio, Resend). +6. **Treat `python_executor` output as untrusted** even if generated by the LLM. The sandbox is best-effort. +7. **Audit log access** — alerts on access to `/api/admin/*` endpoints. + +## Known security debt + +These are tracked publicly because they are not exploitable on their own: + +- Module-level service singletons (`_service = EntityService()`) make per-test override harder and grow into a problem if state is added. +- The TS dashboard stores the JWT in `localStorage` — XSS-recoverable. Migration to httpOnly cookies is planned. +- Backend uses sync psycopg2; under high concurrent load this is a DoS vector before it's a correctness problem. + +Patches welcome. diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 85a7784..99e6517 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,9 +1,130 @@ [project] name = "agentary" -version = "0.1.0" -description = "AI research platform — opportunity scout, research engine, and voice assistant" -requires-python = ">=3.10" +version = "0.2.0" +description = "Autonomous AI research & intelligence platform" +requires-python = ">=3.13" +license = { text = "MIT" } +readme = "../README.md" +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.build_meta" + +# ── Pytest ──────────────────────────────────────────────────────────── [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] +addopts = "-ra --strict-markers --strict-config" +markers = [ + "unit: pure-function/unit tests, no I/O", + "integration: hits a DB, Redis, or other infra", + "slow: long-running tests, excluded from default selection", + "e2e: full-stack scenarios", +] +filterwarnings = [ + "error", + "ignore::DeprecationWarning:passlib.*", + "ignore::DeprecationWarning:google.*", + "ignore::PendingDeprecationWarning", +] + +# ── Coverage ────────────────────────────────────────────────────────── +[tool.coverage.run] +source = ["app"] +branch = true +omit = [ + "app/tests/*", + "tests/*", + "alembic/*", + "**/__init__.py", +] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "raise NotImplementedError", + "if TYPE_CHECKING:", + "if __name__ == .__main__.:", +] +show_missing = true +skip_empty = true +precision = 1 + +# ── Ruff ────────────────────────────────────────────────────────────── +[tool.ruff] +line-length = 100 +target-version = "py313" +extend-exclude = ["alembic/versions"] + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "B", # bugbear + "C4", # comprehensions + "UP", # pyupgrade + "SIM", # simplify + "RUF", # ruff-specific + "ASYNC",# async correctness + "S", # bandit (security) + "T20", # print/pprint detection + "PIE", # misc lints + "RET", # return statements + "TCH", # type-checking import sorting +] +ignore = [ + "E501", # line too long — black handles this + "B008", # function call in default arg — common FastAPI Depends pattern + "S101", # asserts allowed in tests + "S104", # hardcoded bind to 0.0.0.0 in dev + "S105", # hardcoded password fixture in tests + "RET504", # unnecessary assignment before return — sometimes clearer + "UP007", # X | Y syntax — keep Optional[X] supported for now +] + +[tool.ruff.lint.per-file-ignores] +"tests/**" = ["S", "T20", "B"] +"app/tests/**" = ["S", "T20", "B"] +"scripts/**" = ["T20"] + +[tool.ruff.lint.isort] +known-first-party = ["app"] +combine-as-imports = true + +# ── Black ───────────────────────────────────────────────────────────── +[tool.black] +line-length = 100 +target-version = ["py313"] +extend-exclude = "alembic/versions" + +# ── isort ───────────────────────────────────────────────────────────── +[tool.isort] +profile = "black" +line_length = 100 +known_first_party = ["app"] +skip = ["alembic/versions"] + +# ── Mypy ────────────────────────────────────────────────────────────── +[tool.mypy] +python_version = "3.13" +strict_optional = true +warn_unused_ignores = true +warn_redundant_casts = true +warn_return_any = false +disallow_untyped_defs = false +disallow_incomplete_defs = false +check_untyped_defs = true +ignore_missing_imports = true +plugins = ["pydantic.mypy"] +exclude = ["alembic/versions/.*"] + +[[tool.mypy.overrides]] +module = ["tests.*", "app.tests.*"] +disallow_untyped_defs = false + +# ── Bandit (security SAST) ──────────────────────────────────────────── +[tool.bandit] +exclude_dirs = ["tests", "app/tests", "alembic"] +skips = ["B101"] # assert is fine in tests