movidas openspec a /docs

This commit is contained in:
GitHub Copilot 2026-08-07 16:46:33 +02:00
parent 0ef0c9951c
commit aae6b6096e
9 changed files with 486 additions and 0 deletions

View file

@ -0,0 +1,87 @@
## ADDED Requirements
### Requirement: Least-privilege ACLs for group order models
`group.order` and `group.order.slot` SHALL NOT grant write, create or unlink to portal, public
or unassigned internal users. Write/create/unlink SHALL be limited to the group-order manager
group (`website_sale_aplicoop.group_group_order_manager`) and ERP admins. Read MAY be granted to
internal users and portal users through explicit, group-scoped ACLs (no empty `group_id` rows).
Controller mutations continue to run via `sudo()` and MUST NOT depend on broad ACLs.
#### Scenario: Portal user cannot mutate group orders
- **WHEN** a portal user calls the external API to `create`, `write` or `unlink` on `group.order`
or `group.order.slot`
- **THEN** the operation is denied with an `AccessError`
#### Scenario: Manager retains full access
- **WHEN** a user in `group_group_order_manager` creates or edits a `group.order` of their company
- **THEN** the operation succeeds
#### Scenario: Controllers still operate
- **WHEN** an authenticated portal member confirms a cart through the eskaera controller (which
uses `sudo()`)
- **THEN** the underlying `sale.order`/`group.order` writes succeed despite the tighter ACLs
### Requirement: Supplier cost not broadly exposed to portal
Portal users SHALL NOT read `product.supplierinfo` cost/price rows for products or vendors
unrelated to them. The unrestricted record rule (`domain=[(1,'=',1)]`) granting portal read on
`product.supplierinfo` SHALL NOT exist. The product **origin** and **main seller** shown in the
eskaera shop — currently painted from `supplierinfo` — MUST keep working, either by preparing those
values server-side (via `sudo()` in the controller, per the repo's "no logic in QWeb" rule) or via
a record rule scoped to the products the user can access, exposing no supplier cost/price fields.
#### Scenario: Portal user cannot list unrelated supplier cost
- **WHEN** a portal user calls `search_read` on `product.supplierinfo` via the external API
- **THEN** the result does not include supplier cost/price rows of products/vendors unrelated to
the user
#### Scenario: Origin and main seller still render
- **WHEN** a portal member opens the eskaera shop
- **THEN** each product's origin and main seller display correctly, without exposing supplier
cost/price across all vendors
### Requirement: CSRF protection on state-changing eskaera endpoints
State-changing eskaera endpoints SHALL be protected against cross-site request forgery. The
endpoints `/eskaera/save-order`, `/eskaera/confirm`, `/eskaera/clear-cart` and `/eskaera/save-cart`
(which create, confirm, merge or cancel `sale.order` records) SHALL NOT be `type="http"` with
`csrf=False`; they SHALL be `type="json"` (or otherwise validate a CSRF token) so that a cross-site
HTML form POST cannot trigger them.
#### Scenario: Cross-site form POST is rejected
- **WHEN** a logged-in user's browser is induced to submit a cross-site form POST to
`/eskaera/clear-cart` or `/eskaera/confirm`
- **THEN** the request is rejected and no order is cancelled/confirmed
#### Scenario: Legitimate same-origin call works
- **WHEN** the eskaera frontend submits the JSON-RPC request from the same origin
- **THEN** the order is created/confirmed as before
### Requirement: Consumer-group membership enforced on group-order endpoints
Every eskaera endpoint that resolves a `group.order` from a request id SHALL verify that the
requesting user belongs to a consumer group of that order before returning data. This applies to
`load_eskaera_page`, `load_products_ajax`, `add_to_eskaera_cart`, `eskaera_checkout` and
`check_group_order_status`, reusing `_validate_user_group_access` / `_get_consumer_group_for_user`.
Internal users MAY be exempt (matching `eskaera_shop`).
#### Scenario: Non-member gets no cross-group data
- **WHEN** a portal user who is not a member of a group order requests
`/eskaera/<order_id>/load-products-ajax` for that order
- **THEN** the endpoint returns empty/redirect and discloses no catalog or pricing of that order
#### Scenario: Member accesses own group order
- **WHEN** a portal member of the order's consumer group requests the same endpoint
- **THEN** the products and pricing are returned normally
### Requirement: Safe template output in inline scripts
QWeb templates SHALL NOT inject data into inline `<script>` blocks via `t-raw`. JSON payloads for
the client SHALL be emitted through a safe pattern (e.g. `<script type="application/json">` read by
the JS layer).
#### Scenario: Rendered history payload is not raw-injected
- **WHEN** the load-from-history template renders its JSON payload
- **THEN** the payload is emitted through a safe (escaped/typed) mechanism, not `t-raw` inside a
`<script>` tag

View file

@ -0,0 +1,61 @@
## ADDED Requirements
### Requirement: Hardened Odoo configuration
Production `odoo.conf` SHALL set a strong `admin_passwd` (not the default `admin`), `list_db =
False`, a `dbfilter` scoping databases per host, `proxy_mode = True`, `workers > 0`, request/time
limits, `without_demo = True`, and a strong database password (not the literal `odoo`). The
database manager SHALL NOT be reachable publicly.
#### Scenario: Database manager not exposed
- **WHEN** an anonymous client requests `/web/database/manager` on production
- **THEN** it is not served (blocked at the proxy and/or `list_db=False`)
#### Scenario: Real client IP reaches Odoo logs
- **WHEN** a login fails behind the reverse proxy
- **THEN** with `proxy_mode=True` the Odoo log records the real client IP (from `X-Forwarded-For`),
not the proxy IP
### Requirement: Reverse proxy enforces TLS, rate limiting and security headers
The nginx/traefik layer SHALL terminate TLS with strong protocols/ciphers, apply `limit_req` to
`/web/login`, `/web/session/authenticate`, `/jsonrpc` and `/xmlrpc`, and set security headers
(HSTS, X-Frame-Options/CSP, X-Content-Type-Options, Referrer-Policy). The `session_id` cookie
SHALL be `Secure`, `HttpOnly` and `SameSite`.
#### Scenario: Login rate limited
- **WHEN** a client exceeds the configured request rate on `/web/login`
- **THEN** the proxy returns 429 and further attempts are throttled
#### Scenario: Security headers present
- **WHEN** a response is returned from production
- **THEN** it includes HSTS, anti-clickjacking and content-type-options headers, and the session
cookie carries Secure/HttpOnly/SameSite
### Requirement: Brute-force sources banned on web and API
fail2ban SHALL ban source IPs that exceed a failed-authentication threshold, covering both the web
login route and the API authentication routes (`/web/session/authenticate`, `/xmlrpc/2/common`),
using the real client IP.
#### Scenario: Repeated web login failures banned
- **WHEN** a source exceeds the failed-login threshold on `/web/login`
- **THEN** fail2ban bans that IP for the configured duration
#### Scenario: API auth brute force banned
- **WHEN** a source brute-forces `/web/session/authenticate` or `/xmlrpc/2/common`
- **THEN** the same fail2ban jail bans it (attacks via API do not bypass protection)
### Requirement: Secrets and database access hardened
Database and master secrets SHALL be strong and SHALL NOT be committed in plaintext in versioned
files. The PostgreSQL port SHALL NOT be reachable from outside the host/network (firewalled), and
the database user SHALL have least privilege.
#### Scenario: DB port not externally reachable
- **WHEN** an external client attempts to connect to PostgreSQL (5432) on production
- **THEN** the connection is refused/filtered by the firewall
#### Scenario: No plaintext secrets in repo
- **WHEN** the repository is inspected
- **THEN** production database/master passwords are not present in plaintext in versioned files

View file

@ -0,0 +1,54 @@
## ADDED Requirements
### Requirement: Authorization precedes active testing
Active security testing (brute force, fuzzing) SHALL NOT begin until a written authorization
recording scope, source IPs, time window and emergency contact exists, a tested backup/snapshot is
taken, and dedicated test accounts are provisioned. Active tests SHALL run against a staging clone
or within a maintenance window, and SHALL NOT target real user accounts.
#### Scenario: Test blocked without authorization
- **WHEN** an operator attempts an active test without the recorded authorization and snapshot
- **THEN** the runbook procedure halts at the pre-flight gate and the test does not run
#### Scenario: Test uses dedicated accounts on staging
- **WHEN** a brute-force test runs
- **THEN** it targets a dedicated test account on staging (or in a maintenance window), never a
real user
### Requirement: Pre-flight security checklist
The repo SHALL ship a pre-flight checklist (`docs/SECURITY_AUDIT_CHECKLIST.md`) covering the three
fronts — brute force, API/data access, inadvertent vectors — plus infrastructure. Each item SHALL
have an explicit pass/fail criterion.
#### Scenario: Checklist covers all fronts with criteria
- **WHEN** an operator opens the checklist
- **THEN** it contains markable items for each of the three fronts and infra, each with a defined
pass/fail criterion
### Requirement: Non-destructive audit scripts
The repo SHALL provide audit scripts under `scripts/security/` (ACL probe, IDOR probe, brute-force
harness, CSRF PoC, web recon) that are non-destructive and parameterized with test-account
credentials rather than hard-coded production secrets.
#### Scenario: ACL probe reports access matrix
- **WHEN** the ACL probe runs with a portal test account
- **THEN** it reports, per model, whether read/write/create/unlink is allowed or denied, without
modifying production data
#### Scenario: Scripts take credentials as parameters
- **WHEN** a script is invoked
- **THEN** target URL and credentials are supplied as parameters/env, with no secrets committed to
the repo
### Requirement: Findings register tracks resolution
Findings SHALL be recorded in `docs/SECURITY_FINDINGS.md` with severity, evidence and status, and
each SHALL be tracked until resolved and verified.
#### Scenario: Confirmed findings tracked to closure
- **WHEN** the audit completes
- **THEN** the register lists H1H5 (and any new findings) with severity and a status of
resolved/verified or an owner and plan