Files
agent-skills/dogfood/references/session-learnings-ephron-qa.md
Hermes Agent ccc63d1e70 first commit
2026-05-10 13:52:46 +08:00

5.7 KiB

Session Learnings: ephron.ren QA (2026-05-03)

Environment Facts

  • 5 services: Home(8000), Auth(8001), Blog(8002), Canvas(8003), Prompt(8004)
  • Auth: FastAPI + Tortoise ORM, .ephron.ren domain cookie
  • RBAC: user(10) < admin(20) < owner(30)
  • CSRF: {unix_timestamp}:{sha256_hex} format, 75 chars, per-GET refresh
  • Rate limits: login 5/min, register 6/hour, comments 6/min, likes 11/min, save 21/min

High-Value Findings (Reproducible Patterns)

CSP script-src-elem Override (Critical)

  • Symptom: Buttons with onclick="fnName()" do nothing, typeof fnName returns undefined
  • Root cause: script-src-elem 'self' https://cdn.example.com overrides script-src 'unsafe-inline'
  • Detection: curl -sI URL | grep content-security-policy, look for script-src-elem without 'unsafe-inline'
  • Impact: All inline JS blocked → save/publish/discard buttons broken, client-only validation bypassed

CSP form-action Blocks Cross-Origin Redirects (Critical)

  • Date: 2026-05-05
  • Symptom: Login form submits (POST appears in network tab), server sets cookie, but browser stays on login page — no redirect
  • Root cause: CSP form-action 'self' on the 303 redirect response blocks navigation to cross-origin targets
  • Reproduction:
    1. Visit https://auth.ephron.ren/login?redirect=aHR0cHM6Ly93d3cuZXBocm9uLnJlbi8= (redirect=base64 of https://www.ephron.ren/)
    2. Fill username/password, click submit
    3. Browser sends POST to /api/login (same origin allowed)
    4. Server returns 303 to https://www.ephron.ren/ with CSP header containing form-action 'self'
    5. Browser blocks redirect: https://www.ephron.ren/self (https://auth.ephron.ren)
  • Controlled test: Same-origin redirect (auth.ephron.ren/admin) works fine; cross-origin fails
  • Console error: Sending form data to 'https://auth.ephron.ren/api/login' violates the following Content Security Policy directive: "form-action 'self'"
  • Fix: Skip CSP header on 303 responses (empty body, no protection value), or use JS redirect
  • Affected pages: ALL pages that redirect to login with a cross-origin redirect target (www/blog/canvas/prompt subdomains)
  • Key source files: shared/security_headers.py (CSP middleware), auth/src/routes/api.py (login endpoint), auth/src/utils/redirect.py (redirect validation)

Server-Side Password Validation Missing

  • Test: curl -X POST /api/register -d 'username=test&password=123&password_confirm=456&invite_code=CODE'
  • Expected: 400/422 with validation error
  • Actual: 303 redirect (registration succeeds with weak/mismatched passwords)
  • Root cause: Validation only in client JS (blocked by CSP)
  • Lesson: Always test form validation with curl, not just browser

Fulltext Search Silent Failure

  • Test: GET /posts?q=openclaw&mode=fulltext returns 0 results, mode=simple returns 6
  • Root cause: BM25 index not built or jieba tokenizer not installed
  • Detection: Compare simple vs fulltext results for same query

API Auth Order Bug

  • Test: POST /api/service/posts without token, with invalid body
  • Expected: 401 (unauthenticated)
  • Actual: 422 (body validation error — leaks endpoint info)
  • Root cause: Pydantic validation middleware runs before auth middleware

Delegate Task Sizing

  • Curl-only tasks: max ~15-20 test cases per delegate (30+ cases timeout at 600s)
  • Browser tasks: max ~5-8 interactions per delegate (each = 10-30s)
  • Use execute_code with from hermes_tools import terminal for fastest execution
  • Parallel delegates: 3 max, but each should be independently scoped
  • CSRF token changes on every GET request
  • Must use SAME cookie jar for GET (extract token) and POST (submit form)
  • Multiple CSRF tokens on one page (one per form) — extract from specific form context
  • Cross-service cookies: Domain=.ephron.ren should work for all subdomains
  • If cross-service test fails, check cookie jar file, not the cookie itself

Content Restoration Pattern (Playwright)

When homepage/admin content is accidentally overwritten, restore via Playwright:

  1. Prepare JSON with original content (experience/projects/skills/contact/footer)
  2. Login → navigate to admin page
  3. Use page.evaluate() to set form fields by id= (NOT name= — admin forms use id):
    document.getElementById('contact_email').value = '...';
    document.getElementById('footer_copyright').value = '...';
    
  4. Set structured data: initialContent.experience = [...]; renderExperience();
  5. Set is_draft: false for items that should be published
  6. Collect and publish:
    const content = collectFormData();
    document.querySelector('input[name="content_json"]').value = JSON.stringify(content);
    // Find form with content_json input, set action=/admin/publish, submit
    
  7. Verify with curl -s https://site/ checking for restored content strings

Form Field Discovery

  • Admin page fields may use id= instead of name= — check both:
    curl -s -b cookies /admin | grep -oP 'id="[^"]*"' | sort -u
    curl -s -b cookies /admin | grep -oP 'name="[^"]*"' | sort -u
    
  • collectFormData() reads from visible form elements, not hidden content_json
  • Setting content_json directly is overwritten by collectFormData() on submit

Playwright vs curl for Form Submission

  • curl: CSRF token sync is fragile (token changes per-GET, cookie jar must match)
  • Playwright: Handles cookies/CSRF automatically, but CSP may block inline JS
  • Best approach: Use Playwright + page.evaluate() to bypass CSP-blocked functions
  • Pattern: Set form fields via JS → call collectFormData() → set content_json → submit form directly