Last active
February 16, 2026 17:10
-
-
Save drhenner/13161c86fbedb9bd591485cfdb949305 to your computer and use it in GitHub Desktop.
Cursor rules for testing & security
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Ask structured clarifying questions before starting work when there is uncertainty | |
| alwaysApply: true | |
| --- | |
| # Ask Clarifying Questions When Uncertain | |
| When asked to do work and there is ambiguity or uncertainty, **ask clarifying questions BEFORE starting implementation**. Use the `AskQuestion` tool to present structured multiple-choice options. If there is too much uncertainty, instead of doing work give the user a list of uncertain details and ask if they want multiple-choice options to resolve the uncertainty or if they want to determine the answers themselves and come back to this work. | |
| ## When to Ask | |
| - The task has multiple valid interpretations | |
| - The scope is unclear (e.g., "fix this" could mean several things) | |
| - There are meaningful trade-offs between approaches | |
| - You're unsure which files, patterns, or conventions to follow | |
| - The user's intent could go in different directions | |
| ## When There Is Too Much Uncertainty | |
| If there are many unknowns (5+ major open questions), **do not start work**. Instead: | |
| 1. List out the uncertain details clearly | |
| 2. Ask the user whether they want: | |
| - **A)** Multiple-choice options presented via `AskQuestion` to resolve each uncertainty now | |
| - **B)** To go determine the answers themselves and come back when ready | |
| This prevents wasted effort on work that may need to be completely redone. | |
| ## When NOT to Ask | |
| - The task is unambiguous and straightforward | |
| - The user said "just do it" or indicated urgency | |
| - You already have enough context from the conversation | |
| - It's a follow-up where context was already established | |
| ## How to Ask | |
| Always use the `AskQuestion` tool with structured multiple-choice options: | |
| ``` | |
| AskQuestion(questions: [{ | |
| id: "approach", | |
| prompt: "How should I handle the error case?", | |
| options: [ | |
| { id: "a", label: "A) Raise an exception and let the caller handle it" }, | |
| { id: "b", label: "B) Return nil and log a warning" }, | |
| { id: "c", label: "C) Retry up to 3 times before failing" }, | |
| { id: "other", label: "Other (I'll describe in chat)" } | |
| ] | |
| }]) | |
| ``` | |
| ## Rules | |
| 1. **Always include an "Other" option** so the user can type a custom response | |
| 2. **Keep options to 2-5 choices** — enough to cover likely answers, not overwhelming | |
| 3. **Label options A), B), C)...** for easy reference | |
| 4. **Make options concrete** — describe what each choice means, not vague labels | |
| 5. **Batch related questions** into a single `AskQuestion` call when possible | |
| 6. **Briefly explain WHY you're asking** in the prompt text so the user understands the trade-off | |
| ## Example | |
| ``` | |
| AskQuestion(questions: [ | |
| { | |
| id: "scope", | |
| prompt: "This method is called from 3 places. Should I fix all callers or just the one in the stack trace?", | |
| options: [ | |
| { id: "a", label: "A) Fix only the caller in the stack trace (minimal change)" }, | |
| { id: "b", label: "B) Fix all 3 callers for consistency" }, | |
| { id: "c", label: "C) Fix the method itself so no caller needs to change" }, | |
| { id: "other", label: "Other (I'll describe in chat)" } | |
| ] | |
| }, | |
| { | |
| id: "testing", | |
| prompt: "How thorough should the test coverage be?", | |
| options: [ | |
| { id: "a", label: "A) Just test the bug fix (1 test)" }, | |
| { id: "b", label: "B) Test the fix + key edge cases (3-5 tests)" }, | |
| { id: "other", label: "Other (I'll describe in chat)" } | |
| ] | |
| } | |
| ]) | |
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Manage CODEOWNERS when creating new files | |
| alwaysApply: true | |
| --- | |
| # CODEOWNERS Management | |
| When creating a new file, **always** add an entry to `.github/CODEOWNERS`. | |
| ## Workflow | |
| 1. **Ask about ownership**: "Should this file be owned by @zendesk/fang?" | |
| 2. **If not fang, suggest logical teams** based on the file path/type: | |
| - **Rules/automation**: @zendesk/fang, @zendesk/libretto | |
| - **Authentication/SSO**: @zendesk/authentication | |
| - **Users/roles**: @zendesk/users-platform | |
| - **Tickets**: @zendesk/ticket-platform, @zendesk/ticket-product | |
| - **Platform objects/custom fields**: @zendesk/vinyl, @zendesk/libretto | |
| - **Operations/infra**: @zendesk/classic-operations, @zendesk/squonk | |
| - **Search**: @zendesk/support-search | |
| - **Views**: @zendesk/support-views | |
| - **GraphQL**: @zendesk/skvader, @zendesk/one-graph | |
| - **Rails/Ruby core**: @zendesk/ruby-core | |
| - **Testing/CI**: @zendesk/classic-operations | |
| 3. **Add entry in alphabetical order**: The file must be sorted alphabetically | |
| 4. **Format**: `path/to/file @zendesk/team-name` | |
| ## Example | |
| ``` | |
| # User creates: app/jobs/ticket_automation_job.rb | |
| Ask: "Should this be owned by @zendesk/fang?" | |
| If yes: Add `app/jobs/ticket_automation_job.rb @zendesk/fang` | |
| If no: "This looks like it could be: @zendesk/fang (automation), @zendesk/ticket-platform (tickets), or @zendesk/classic-operations (general). Which team should own it?" | |
| ``` | |
| ## Important Notes | |
| - CODEOWNERS must be kept in **alphabetical order** (see file header comment) | |
| - Use full paths, not globs (unless following existing patterns in the file) | |
| - Multiple teams can own the same file (space-separated) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Guidelines for testing controller params and strong parameters | |
| globs: "{test/**/controllers/**/*_test.rb,components/**/test/**/controllers/**/*_test.rb}" | |
| alwaysApply: false | |
| --- | |
| # Controller Test Params Guidelines | |
| ## Don't Stub Params | |
| Never stub or mock `params` in controller tests. Pass real params through the request: | |
| ```ruby | |
| # ❌ BAD - stubbing params | |
| controller.stubs(:params).returns(ActionController::Parameters.new(name: "test")) | |
| # ✅ GOOD - pass params through the request | |
| post :create, params: { name: "test" } | |
| ``` | |
| ## Verify Strong Parameters Indirectly | |
| Don't write separate tests just to verify `allow_parameters`/`permit`. Instead, verify params are used correctly through integration: | |
| ```ruby | |
| # ❌ UNNECESSARY - testing permit directly | |
| test "permits name parameter" do | |
| params = ActionController::Parameters.new(name: "test") | |
| assert controller.send(:allowed_params).permitted? | |
| end | |
| # ✅ GOOD - verify indirectly through actual behavior | |
| test "creates resource with provided name" do | |
| post :create, params: { name: "test" } | |
| assert_equal "test", Resource.last.name | |
| end | |
| ``` | |
| ## Key Principles | |
| 1. **Test behavior, not implementation** - verify the endpoint does what it should | |
| 2. **Pass real params** - let Rails handle parameter filtering naturally | |
| 3. **Assert on outcomes** - check that permitted params affect the result | |
| 4. **Unpermitted params should be ignored** - test that extra params don't cause errors |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Log corrections from the user as beans tagged "correction" for later review and potential promotion to Cursor rules | |
| alwaysApply: true | |
| --- | |
| # Corrections Pipeline: Beans to Rules | |
| When the user corrects you — tells you that you did something wrong, should have done something differently, or points out a pattern/convention you missed — **immediately log the correction as a bean** so it can be reviewed later and potentially promoted to a permanent Cursor rule. | |
| ## What Counts as a Correction | |
| - "No, you should have done X instead of Y" | |
| - "That's not how we do it here" | |
| - "Always use X pattern in this codebase" | |
| - "Don't do X, do Y" | |
| - "You keep making this mistake" | |
| - Explicit pushback on an approach, convention, or assumption you made | |
| - Telling you to redo work because you missed a convention | |
| ## What Does NOT Count | |
| - Normal task instructions ("please rename this variable") | |
| - Clarifications that aren't correcting a mistake ("I meant the other file") | |
| - Opinions or preferences being stated for the first time without correcting you | |
| - Feedback on work quality that isn't about a repeatable pattern | |
| ## How to Log a Correction | |
| When you recognize a correction, create a bean: | |
| ```bash | |
| beans create "Correction: <short description>" \ | |
| -t task \ | |
| --tag correction \ | |
| -s todo \ | |
| -d "## What Happened | |
| <what you did wrong or missed> | |
| ## Correct Behavior | |
| <what the user said to do instead> | |
| ## Context | |
| <files, patterns, or situations where this applies> | |
| ## Rule Candidate? | |
| - [ ] Review whether this should become a .cursor/rules/ rule" | |
| ``` | |
| ### Field Guidelines | |
| - **Title**: Start with `Correction:` followed by a concise description | |
| - **Type**: Always `task` | |
| - **Tag**: Always `correction` | |
| - **Status**: `todo` (meaning: not yet reviewed for rule promotion) | |
| - **Priority**: `normal` by default. Use `high` if the user says "you keep doing this" or it's the second time you've been corrected on the same thing. | |
| ## Reviewing Corrections | |
| When the user asks to review corrections or decide which should become rules: | |
| ```bash | |
| # List all unreviewed corrections | |
| beans list --json --tag correction -s todo | |
| # Show details | |
| beans show <id> | |
| ``` | |
| ### Promotion to Rule | |
| If a correction should become a permanent rule: | |
| 1. Create the `.cursor/rules/<descriptive-name>.mdc` file | |
| 2. Mark the bean as `completed` with a summary: | |
| ```bash | |
| beans update <id> -s completed --body-append "## Summary of Changes\nPromoted to rule: .cursor/rules/<rule-name>.mdc" | |
| ``` | |
| ### Dismissal | |
| If a correction is one-off or not worth a rule: | |
| ```bash | |
| beans update <id> -s scrapped --body-append "## Reasons for Scrapping\n<why this doesn't need a rule>" | |
| ``` | |
| ## Important | |
| - **Log the correction immediately** when it happens, don't wait until the end of the conversation | |
| - **Be honest about what you got wrong** — the bean should accurately capture the mistake so the rule can prevent it in the future | |
| - **Don't ask permission** to log the correction — just do it alongside your corrected work | |
| - **Do tell the user** you've logged it, with a brief one-liner like: "Logged that as a correction bean (`<id>`) for future rule review." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Do not push code to GitHub unless explicitly requested | |
| alwaysApply: true | |
| --- | |
| # Git Push Policy | |
| **Never push code to GitHub unless the user explicitly asks you to push.** | |
| ## Allowed Git Operations (without asking) | |
| - `git status` | |
| - `git diff` | |
| - `git log` | |
| - `git add` | |
| - `git commit` | |
| - `git branch` | |
| - `git checkout` | |
| - `git stash` | |
| ## Requires Explicit User Request | |
| - `git push` (any variant) | |
| - `git push origin` | |
| - `git push --force` | |
| - `git push -u origin` | |
| ## Example | |
| ``` | |
| ❌ BAD: Automatically pushing after a commit | |
| "Let me commit and push this change..." | |
| git commit -m "Fix bug" && git push | |
| ✅ GOOD: Commit only, inform user | |
| "I've committed the change. Let me know when you'd like me to push it." | |
| git commit -m "Fix bug" | |
| ``` | |
| When you've made commits that are ready to push, inform the user and wait for their explicit instruction to push. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Format Jira story links to display only ticket numbers | |
| alwaysApply: true | |
| --- | |
| # Jira Link Formatting | |
| When creating or updating Jira ticket descriptions with story links, use explicit link syntax to display only the ticket number instead of the full story title. | |
| ## Format | |
| Use the full URL format with pipe syntax: | |
| ``` | |
| [TICKET-KEY|https://zendesk.atlassian.net/browse/TICKET-KEY] | |
| ``` | |
| This displays as a clickable "TICKET-KEY" link without expanding to the full story title. | |
| ## Examples | |
| **✅ GOOD - Displays as "CW-4280":** | |
| ``` | |
| [CW-4280|https://zendesk.atlassian.net/browse/CW-4280] (Story 1: PLOB Schema) | |
| ``` | |
| **❌ BAD - Displays as "Story 1: TaskObjectLink PLOB Schema & Installation":** | |
| ``` | |
| [CW-4280] (Story 1: PLOB Schema) | |
| ``` | |
| **❌ BAD - Displays as literal text "[CW-4280|CW-4280]":** | |
| ``` | |
| [CW-4280|CW-4280] | |
| ``` | |
| ## Usage Pattern | |
| When listing dependencies or in narrative text: | |
| ``` | |
| After [CW-4281|https://zendesk.atlassian.net/browse/CW-4281] completes, run [CW-4282|https://zendesk.atlassian.net/browse/CW-4282] and [CW-4283|https://zendesk.atlassian.net/browse/CW-4283]. | |
| ``` | |
| Add descriptive text in parentheses after the link if needed: | |
| ``` | |
| [CW-4299|https://zendesk.atlassian.net/browse/CW-4299] (Story 21: GraphQL) | |
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Don't add story points to Jira stories unless explicitly requested | |
| alwaysApply: true | |
| --- | |
| # Jira Story Points Policy | |
| **Never add story points when creating new Jira stories unless the user explicitly asks you to point the story.** | |
| ## Default Behavior | |
| When creating Jira stories with `jira_create_issue`, omit the story points field from `additional_fields`. | |
| ## Examples | |
| **❌ BAD - Adding story points without being asked:** | |
| ``` | |
| jira_create_issue( | |
| project_key: "CW", | |
| summary: "Implement feature X", | |
| issue_type: "Story", | |
| additional_fields: { | |
| "customfield_10016": 5 # Story points - DON'T DO THIS | |
| } | |
| ) | |
| ``` | |
| **✅ GOOD - No story points by default:** | |
| ``` | |
| jira_create_issue( | |
| project_key: "CW", | |
| summary: "Implement feature X", | |
| issue_type: "Story" | |
| # No additional_fields with story points | |
| ) | |
| ``` | |
| **✅ GOOD - Only add when explicitly requested:** | |
| ``` | |
| User: "Create a story for feature X and point it at 5" | |
| jira_create_issue( | |
| project_key: "CW", | |
| summary: "Implement feature X", | |
| issue_type: "Story", | |
| additional_fields: { | |
| "customfield_10016": 5 # OK - user explicitly asked for this | |
| } | |
| ) | |
| ``` | |
| ## Rationale | |
| Story pointing should be done thoughtfully by humans during sprint planning, not automatically by AI. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Ask clarifying questions when starting work on a new Jira ticket | |
| alwaysApply: true | |
| --- | |
| # Jira Ticket Clarification Questions | |
| When the user mentions starting work on a new Jira ticket (e.g., "working on CW-1234", "starting ticket XYZ"), ask clarifying questions before diving into implementation. | |
| ## Acceptance Criteria Questions | |
| - What is the expected behavior when this is complete? | |
| - Are there specific edge cases to handle? | |
| - What should happen in error scenarios? | |
| - Are there any performance requirements? | |
| - What testing approach is expected (unit tests, integration tests, manual testing)? | |
| ## Context Gathering Questions | |
| - Are there related tickets or prior work I should reference? | |
| - Are there existing code patterns in this area I should follow? | |
| - Which components/files are likely affected? | |
| - Are there any dependencies on other teams or services? | |
| - Is this behind a feature flag (Arturo)? | |
| ## When to Ask | |
| Ask these questions when: | |
| - User explicitly mentions a Jira ticket number | |
| - User describes a new feature or bug fix without providing context | |
| - The scope seems ambiguous | |
| ## When to Skip | |
| Skip clarifying questions when: | |
| - User has already provided detailed context | |
| - User says "just do it" or indicates urgency | |
| - It's a follow-up to an existing conversation with established context |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Default formatting guidelines for writing to Logseq via MCP tools | |
| alwaysApply: true | |
| --- | |
| # Logseq MCP Default Formatting | |
| **Always follow these rules when using Logseq MCP tools.** | |
| ## Core Rules | |
| 1. **One item per block** - Each bullet point, sentence, or piece of content must be its own separate block | |
| 2. **Bold for section headers** - Use `**Section Title**` instead of markdown `##` or `###` | |
| 3. **Italics for sub-headers** - Use `*Sub-section*` for secondary headings | |
| 4. **Horizontal rules for separation** - Use `---` blocks between major sections | |
| 5. **No newlines in content** - Never include `\n` in block content strings | |
| ## Formatting Reference | |
| | Element | Use This | NOT This | | |
| |---------|----------|----------| | |
| | Section header | `**Summary**` | `## Summary` | | |
| | Sub-header | `*Details:*` | `### Details` | | |
| | Section separator | `---` | (nothing) | | |
| | Multiple items | Separate blocks | `item1\nitem2` | | |
| ## Standard Page Structure | |
| ``` | |
| Block 1: "**Summary**" | |
| Block 2: "Key point one" | |
| Block 3: "Key point two" | |
| Block 4: "---" | |
| Block 5: "**Next Section**" | |
| Block 6: "Content for this section" | |
| Block 7: "*Sub-header:*" | |
| Block 8: "Sub-content here" | |
| ``` | |
| ## Workflow | |
| 1. Create page with `logseq_create_page` including properties (tags, status, date, etc.) | |
| 2. Add section headers as bold text blocks | |
| 3. Add content items as separate blocks (one per block) | |
| 4. Use `---` blocks to visually separate major sections | |
| 5. Use `is_page_block: true` with page name as `parent_block` | |
| ## Why These Rules | |
| - Logseq shows "full content not displayed" warning for multi-line content | |
| - Markdown `##` headers don't render as headers in Logseq blocks | |
| - Bold text renders correctly and looks good as section headers | |
| - One item per block follows Logseq's outliner philosophy |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Append highlights to personal changelog on every commit | |
| alwaysApply: true | |
| --- | |
| # Personal Changelog | |
| Every time the user asks to commit code, append a summary to `.cursor/CHANGELOG.md` BEFORE creating the commit. | |
| ## Changelog Location | |
| `.cursor/CHANGELOG.md` in the current repo root (gitignored, personal only). | |
| ## Format | |
| Append a new entry at the end of the file (before any trailing whitespace), using this format: | |
| ```markdown | |
| ## YYYY-MM-DD | branch-name | |
| **Commit:** Short commit message here | |
| - Highlight of what changed and why | |
| - Another highlight if multiple things changed | |
| - Reference ticket numbers (e.g., CW-1234) when applicable | |
| --- | |
| ``` | |
| ## Rules | |
| 1. **Always append, never overwrite** existing entries | |
| 2. **Focus on the "why" and impact**, not just file names | |
| 3. **Keep it concise** - 2-5 bullet points per commit | |
| 4. **Include ticket/PR references** when available | |
| 5. **Group related changes** if committing multiple files | |
| 6. Do NOT include the changelog update in the git commit itself (it's gitignored anyway) | |
| 7. Write in first person ("Added...", "Fixed...", "Refactored...") | |
| ## Example | |
| ```markdown | |
| ## 2025-02-05 | drhenner/SLA_policy_confusion_CW-4129 | |
| **Commit:** Bump protobuf clients to 9.1583.0 | |
| - Updated zendesk_protobuf_clients and zendesk_grpc_clients for new SlaPolicyChanged schema | |
| - Required google-protobuf bump from 3.x to 4.x for compatibility | |
| - Supports CW-4129: SLA policy assignment tracking improvements | |
| --- | |
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Add accomplishments to quarterly notes with guided questions | |
| alwaysApply: true | |
| --- | |
| # Quarterly Accomplishments Notes | |
| When the user asks to "add to notes", "log this accomplishment", "record this for quarterly review", or similar: | |
| ## Notes File Location | |
| `/Users/dhenner/Code/drhenner/quarterly_accomplishments/config/notes.yml` | |
| ## Workflow | |
| 1. **Determine category** by asking: | |
| ``` | |
| Which category best fits this accomplishment? | |
| 1. Slack/helping others | |
| 2. Mentoring | |
| 3. Presentation/knowledge sharing | |
| 4. Process improvement | |
| 5. Other (cross-team, incident, hiring, etc.) | |
| ``` | |
| 2. **Ask category-specific follow-ups:** | |
| ### For Slack/Helping Others: | |
| - What channel or context? (e.g., #classic-dev, DM, meeting) | |
| - Who did you help? (person, team, or "multiple engineers") | |
| - What was the impact? (time saved, unblocked work, etc.) | |
| ### For Mentoring: | |
| - Who did you mentor? (new hire, intern, team member) | |
| - What did you help with? (onboarding, code review, architecture) | |
| ### For Presentations: | |
| - What was the title/topic? | |
| - Who was the audience? (team, org, external) | |
| - When was it? (approximate date) | |
| ### For Process Improvements: | |
| - What did you improve? (runbook, tooling, documentation) | |
| - What was the impact? (time saved, errors prevented) | |
| ### For Other: | |
| - What category? (cross-team, incident response, hiring, etc.) | |
| - Brief description of contribution | |
| 3. **Format and add** to the appropriate section in notes.yml | |
| ## Notes Format | |
| ```yaml | |
| slack_highlights: | |
| - channel: "#channel-name" | |
| description: "What you helped with" | |
| impact: "Result/outcome" | |
| mentoring: | |
| - who: "Person or group" | |
| description: "What you did" | |
| presentations: | |
| - title: "Title" | |
| audience: "Who attended" | |
| date: "YYYY-MM-DD" | |
| process_improvements: | |
| - description: "What you improved" | |
| impact: "How it helped" | |
| other: | |
| - category: "Category name" | |
| description: "What you did" | |
| impact: "Optional impact" | |
| ``` | |
| ## Example Interaction | |
| User: "Add to notes - just helped Sarah debug the SLA calculation issue" | |
| Assistant response: | |
| 1. Ask: "Which channel/context was this in?" | |
| 2. Ask: "What was the impact - did it unblock her work?" | |
| 3. Add entry to slack_highlights section |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Security considerations when reviewing code changes and PRs | |
| globs: ["**/*.rb", "**/*.erb", "**/*.js", "**/*.ts"] | |
| --- | |
| # Security Review Checklist | |
| When reviewing code changes or PRs, **always consider these security concerns**: | |
| ## 🔐 Authorization & Access Control | |
| ### Account Scoping (CRITICAL for Multi-tenancy) | |
| Every database query must be scoped to the current account to prevent data leakage between tenants: | |
| ```ruby | |
| # ✅ Good - scoped to account | |
| account.tickets.find(params[:id]) | |
| Ticket.where(account_id: current_account.id).find(params[:id]) | |
| # ❌ Bad - unscoped query can access other accounts' data | |
| Ticket.find(params[:id]) | |
| Ticket.where(id: params[:id]).first | |
| ``` | |
| ### Permission Checks | |
| Verify the user has permission to perform the action: | |
| ```ruby | |
| # ✅ Good - explicit authorization check | |
| def update | |
| @ticket = current_account.tickets.find(params[:id]) | |
| authorize! :update, @ticket # or use access policies | |
| # ... | |
| end | |
| # ❌ Bad - no authorization check | |
| def update | |
| @ticket = Ticket.find(params[:id]) | |
| @ticket.update(ticket_params) | |
| end | |
| ``` | |
| ## 💉 Injection Vulnerabilities | |
| ### SQL Injection | |
| Never interpolate user input directly into SQL: | |
| ```ruby | |
| # ✅ Good - parameterized query | |
| User.where("email = ?", params[:email]) | |
| User.where(email: params[:email]) | |
| # ❌ Bad - SQL injection vulnerability | |
| User.where("email = '#{params[:email]}'") | |
| User.where("email = " + params[:email]) | |
| ``` | |
| ### Command Injection | |
| Never pass user input to shell commands: | |
| ```ruby | |
| # ✅ Good - use arrays to avoid shell interpretation | |
| system("convert", input_file, output_file) | |
| # ❌ Bad - command injection vulnerability | |
| system("convert #{params[:filename]}") | |
| `convert #{params[:filename]}` | |
| ``` | |
| ## 🌐 Cross-Site Scripting (XSS) | |
| ### Output Encoding | |
| All user-controlled data must be escaped in views: | |
| ```erb | |
| <%# ✅ Good - auto-escaped %> | |
| <%= user.name %> | |
| <%# ❌ Bad - raw output, XSS vulnerability %> | |
| <%= raw user.bio %> | |
| <%= user.bio.html_safe %> | |
| <%== user.comment %> | |
| ``` | |
| ### Be Careful With: | |
| - `raw()`, `html_safe`, `<%==` in ERB | |
| - `dangerouslySetInnerHTML` in React | |
| - Rendering user content in JavaScript contexts | |
| ## 🔑 Sensitive Data Handling | |
| ### Don't Log Sensitive Data | |
| ```ruby | |
| # ❌ Bad - logging sensitive information | |
| Rails.logger.info("User login: #{user.email}, password: #{password}") | |
| Rails.logger.info("API key: #{api_key}") | |
| # ✅ Good - filter sensitive params | |
| # Ensure config/initializers/filter_parameter_logging.rb includes sensitive fields | |
| ``` | |
| ### Don't Expose Internal IDs/Data in Errors | |
| ```ruby | |
| # ❌ Bad - exposes internal details | |
| render json: { error: e.message, backtrace: e.backtrace } | |
| # ✅ Good - generic error message | |
| render json: { error: "An error occurred" }, status: :internal_server_error | |
| # Log details internally via Sentry/zendesk_exceptions | |
| ``` | |
| ## 🛡️ Mass Assignment | |
| ### Strong Parameters | |
| Always whitelist permitted attributes: | |
| ```ruby | |
| # ✅ Good - explicit permit | |
| def ticket_params | |
| params.require(:ticket).permit(:subject, :description, :priority) | |
| end | |
| # ❌ Bad - permits everything | |
| params.require(:ticket).permit! | |
| params[:ticket].to_unsafe_h | |
| ``` | |
| ### Watch for Privilege Escalation | |
| ```ruby | |
| # ❌ Dangerous - user could set admin: true | |
| params.require(:user).permit(:name, :email, :admin, :role) | |
| # ✅ Good - sensitive fields handled separately | |
| params.require(:user).permit(:name, :email) | |
| # Admin/role changes require separate authorization | |
| ``` | |
| ## 🔄 CSRF & Request Forgery | |
| ### State-Changing Actions | |
| All state-changing actions should: | |
| - Use POST/PUT/PATCH/DELETE (not GET) | |
| - Verify CSRF tokens (Rails does this by default) | |
| ```ruby | |
| # ❌ Bad - GET request changes state | |
| get '/users/:id/delete' | |
| # ✅ Good - proper HTTP verb | |
| delete '/users/:id' | |
| ``` | |
| ## 🔗 Insecure Direct Object References (IDOR) | |
| ### Always Verify Ownership | |
| ```ruby | |
| # ❌ Bad - IDOR vulnerability | |
| @attachment = Attachment.find(params[:attachment_id]) | |
| # ✅ Good - scoped through association | |
| @attachment = @ticket.attachments.find(params[:attachment_id]) | |
| ``` | |
| ## 📦 Dependency Security | |
| ### When Adding/Updating Gems | |
| - Check for known vulnerabilities: `bundle audit` | |
| - Review gem's security history | |
| - Prefer well-maintained, widely-used gems | |
| ## 🚨 Red Flags to Watch For | |
| | Pattern | Risk | | |
| |---------|------| | |
| | `Ticket.find(params[:id])` | Missing account scope | | |
| | `raw()`, `html_safe` | Potential XSS | | |
| | String interpolation in SQL | SQL injection | | |
| | `permit!` or `to_unsafe_h` | Mass assignment | | |
| | `system()` with string arg | Command injection | | |
| | Logging request params | Sensitive data exposure | | |
| | `skip_before_action :verify_authenticity_token` | CSRF bypass | | |
| | Hardcoded secrets/API keys | Credential exposure | | |
| ## 📋 PR Security Checklist | |
| Before approving any PR, verify: | |
| - [ ] All queries are scoped to account | |
| - [ ] Authorization checks are in place | |
| - [ ] User input is validated and sanitized | |
| - [ ] No raw SQL with string interpolation | |
| - [ ] No `html_safe`/`raw` on user content | |
| - [ ] Strong parameters properly configured | |
| - [ ] Sensitive data not logged or exposed | |
| - [ ] New gems have been security-reviewed |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| description: Testing conventions for Zendesk Classic - prefer real objects, use Arturo test helpers | |
| globs: ["test/**/*.rb", "components/**/test/**/*.rb"] | |
| --- | |
| # Testing Conventions | |
| ## Prefer Real Objects Over Stubs/Mocks | |
| When writing tests, **prefer using real objects and fixtures over stubs and mocks**: | |
| ### ✅ Do: Use Real Objects | |
| ```ruby | |
| # Good - use fixtures and real objects | |
| test "user can update ticket" do | |
| account = accounts(:acme) | |
| user = users(:admin) | |
| ticket = tickets(:problem_ticket) | |
| ticket.update!(subject: "New subject") | |
| assert_equal "New subject", ticket.reload.subject | |
| end | |
| # Good - create objects with FactoryBot when needed | |
| test "creates a new ticket" do | |
| account = accounts(:acme) | |
| ticket = create(:ticket, account: account, subject: "Test") | |
| assert ticket.persisted? | |
| end | |
| ``` | |
| ### ❌ Avoid: Excessive Stubbing | |
| ```ruby | |
| # Avoid - stubbing everything makes tests brittle and hides real behavior | |
| test "user can update ticket" do | |
| ticket = stub(id: 1, subject: "Old") | |
| ticket.stubs(:update!).returns(true) | |
| ticket.stubs(:reload).returns(ticket) | |
| ticket.stubs(:subject).returns("New subject") | |
| # This test doesn't actually verify anything real | |
| end | |
| ``` | |
| ### When Stubs ARE Appropriate | |
| - External API calls (use WebMock for HTTP) | |
| - Time-sensitive operations (`travel_to`) | |
| - Expensive operations that are not the focus of the test | |
| - Third-party services | |
| ## Testing Arturo Feature Flags | |
| When testing code behind an Arturo feature flag, **always test both enabled and disabled states** using the helpers from `ArturoTestHelper`. | |
| ### Required Pattern: Test Both States | |
| ```ruby | |
| class MyFeatureTest < ActiveSupport::TestCase | |
| # Test with Arturo DISABLED (old behavior) | |
| describe_with_arturo_disabled :my_feature_flag do | |
| test "behaves the old way when feature is off" do | |
| # Assert old behavior | |
| end | |
| end | |
| # Test with Arturo ENABLED (new behavior) | |
| describe_with_arturo_enabled :my_feature_flag do | |
| test "behaves the new way when feature is on" do | |
| # Assert new behavior | |
| end | |
| end | |
| end | |
| ``` | |
| ### Or Use the Combined Helper | |
| ```ruby | |
| class MyFeatureTest < ActiveSupport::TestCase | |
| # Tests both states automatically | |
| describe_with_and_without_arturo_enabled :my_feature_flag do | |
| test "works correctly" do | |
| if @arturo_enabled | |
| # Assert new behavior | |
| else | |
| # Assert old behavior | |
| end | |
| end | |
| end | |
| end | |
| ``` | |
| ### Available Arturo Test Helpers | |
| #### Option 1: Describe Blocks (Preferred for grouping tests) | |
| | Helper | Use Case | | |
| |--------|----------| | |
| | `describe_with_arturo_enabled :flag` | Test with feature ON | | |
| | `describe_with_arturo_disabled :flag` | Test with feature OFF | | |
| | `describe_with_and_without_arturo_enabled :flag` | Test both states | | |
| | `describe_with_arturo_enabled_for_pod :flag` | Test pod-specific rollout | | |
| | `describe_with_arturos [{arturo: :a, enabled: true}, ...]` | Multiple flags | | |
| #### Option 2: Enable/Disable Within Tests | |
| For more granular control within individual tests, use `Arturo.enable_feature!` and `Arturo.disable_feature!`: | |
| ```ruby | |
| test "behaves differently based on feature flag" do | |
| # Test with feature disabled | |
| Arturo.disable_feature!(:my_feature_flag) | |
| assert_equal "old behavior", subject.do_something | |
| # Test with feature enabled | |
| Arturo.enable_feature!(:my_feature_flag) | |
| assert_equal "new behavior", subject.do_something | |
| end | |
| ``` | |
| You can also scope to specific subdomains or pods: | |
| ```ruby | |
| test "feature enabled for specific subdomain" do | |
| Arturo.enable_feature_for!(:my_feature_flag, subdomains: ['acme'], pods: [1]) | |
| # ... | |
| end | |
| ``` | |
| **Pros of enable/disable approach:** | |
| - More accurate tests, less error prone | |
| - Can be nested | |
| - State automatically resets after test completes | |
| - Works well for testing transitions within a single test | |
| **Con:** Requires database writes | |
| ### ⚠️ Important Warning | |
| The Arturo **must be registered in `app/models/account/capabilities.rb`** before using these test helpers. Failing to do so causes false positives, incidents, and failed deploys. | |
| ```ruby | |
| # In app/models/account/capabilities.rb | |
| feature :my_feature_flag, arturo: !Rails.env.test? # enabled by default in tests | |
| ``` | |
| ### Accessing Arturo State in Tests | |
| Inside `describe_with_arturo_*` blocks, you can check the current state: | |
| ```ruby | |
| describe_with_and_without_arturo_enabled :my_feature do | |
| test "conditional behavior" do | |
| result = some_method_that_checks_arturo | |
| if @arturo_enabled | |
| assert_equal "new result", result | |
| else | |
| assert_equal "old result", result | |
| end | |
| end | |
| end | |
| ``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment