Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Expert in Odoo access control: ir.model.access.csv, record rules (ir.rule), groups, and multi-company security patterns.
.claude/skills/odoo-security-rules/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-19 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-04 | ✓→✓ | = Same ✓ | — | — |
| case-17 | ✗→✗ | = Same ✗ | — | — |
Security in Odoo is managed at two levels: model-level access (who can read/write which models) and record-level rules (which records a user can see). This skill helps you write correct ir.model.access.csv entries and ir.rule domain-based record rules.
@odoo-security-rules and describe the access scenario.csvid,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_hospital_patient_user,hospital.patient.user,model_hospital_patient,base.group_user,1,0,0,0 access_hospital_patient_manager,hospital.patient.manager,model_hospital_patient,base.group_erp_manager,1,1,1,1
> Note: Use base.group_erp_manager for ERP managers, not base.group_system — that group is reserved for Odoo's technical superusers. Always create a custom group for module-specific manager roles: > > xml > <record id="group_hospital_manager" model="res.groups"> > <field name="name">Hospital Manager</field> > <field name="category_id" ref="base.module_category_hidden"/> > </record> >
xml<record id="rule_hospital_patient_own" model="ir.rule"> <field name="name">Hospital Patient: Own Records Only</field> <field name="model_id" ref="model_hospital_patient"/> <field name="domain_force">[('create_uid', '=', user.id)]</field> <field name="groups" eval="[(4, ref('base.group_user'))]"/> <field name="perm_read" eval="True"/> <field name="perm_write" eval="True"/> <field name="perm_create" eval="True"/> <field name="perm_unlink" eval="False"/> </record>
> Important: If you omit <field name="groups">, the rule becomes global and applies to ALL users, including admins. Always assign a group unless you explicitly intend a global restriction.
xml<record id="rule_hospital_patient_company" model="ir.rule"> <field name="name">Hospital Patient: Multi-Company</field> <field name="model_id" ref="model_hospital_patient"/> <field name="domain_force"> ['|', ('company_id', '=', False), ('company_id', 'in', company_ids)] </field> <field name="groups" eval="[(4, ref('base.group_user'))]"/> </record>
company_ids (plural) in multi-company rules — it includes all companies the user belongs to.sudo() bypasses all record rules entirely.perm_unlink = 1 to regular users unless deletion is explicitly required by the business process.group_id blank in ir.model.access.csv unless you intend to grant public (unauthenticated) access.base.group_system for module managers — that grants full technical access including server configurations.ir.model.fields read/write restrictions) — those require custom OWL or Python overrides.base.group_portal.sudo() — any code running in superuser context ignores all ir.rule entries.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +14 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.