feat: priority-based rules sections with hard/soft model#2
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 59c6a52594
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Local API auth: if a CE_API_KEY is configured, require a Bearer token | ||
| // on every API request. Static UI files and auth endpoints are exempt. | ||
| const token = getAuthToken(); | ||
| if (token && url.pathname.startsWith('/api/') && !url.pathname.startsWith('/api/auth/')) { |
There was a problem hiding this comment.
Enforce bearer auth on token-mutation auth endpoints
The new auth gate explicitly skips every /api/auth/* route, which lets any local caller mutate auth state without credentials. In practice, an unauthenticated process can call /api/auth/remove (or rotate via /api/auth/generate) and then access protected /api/* data, so the protection is trivially bypassed in the same threat model this feature is meant to defend (other local processes).
Useful? React with 👍 / 👎.
|
|
||
| /** @param {string} name @returns {RuleData | null} */ | ||
| function readRuleFile(name) { | ||
| const filePath = path.join(RULES_DIR, `${name}.json`); |
There was a problem hiding this comment.
Sanitize rule file names before filesystem reads
readRuleFile joins untrusted name directly into a path without applying sanitizeName or a base-dir containment check. Because the route decodes path components first, inputs like %2e%2e%2f... become ../... and can escape data/rules, allowing reads of arbitrary *.json files via /api/rule-files/:name (and via combineRuleFiles in publish), which is a path traversal data exposure.
Useful? React with 👍 / 👎.
| currentRuleFile = result.name; | ||
| if (input) input.value = ''; | ||
| await loadRuleFileList(); | ||
| RulesLab.setDraft(emptyRules); |
There was a problem hiding this comment.
Load newly created rule file into ConfigTab state
After creating a rule file, the UI only calls RulesLab.setDraft(emptyRules) and never refreshes ConfigTab’s _currentServerData for that file. The next save therefore merges non-active priorities from the previously loaded file (stored = _currentServerData || RS.get()), which can silently copy old rule text into the new file on first save.
Useful? React with 👍 / 👎.
| returnErrors: true, | ||
| }); | ||
| if (result?.ok) return result; | ||
| return { ok: true }; |
There was a problem hiding this comment.
Return write failures from rule-file update API wrapper
DS.updateRuleFile reports { ok: true } even when the backend request fails. That makes the caller treat failed writes (for example 401/auth failures or server-side validation errors) as successful, so the UI says the rule was saved while disk state on the server is unchanged.
Useful? React with 👍 / 👎.
| // Local API auth: if a CE_API_KEY is configured, require a Bearer token | ||
| // on every API request. Static UI files and auth endpoints are exempt. | ||
| const token = getAuthToken(); | ||
| if (token && url.pathname.startsWith('/api/') && !url.pathname.startsWith('/api/auth/')) { |
There was a problem hiding this comment.
Keep built-in key management reachable after enabling API auth
The new global auth check now requires bearer auth on /api/keys/*, but the Config tab still calls those endpoints with raw fetch('/api/keys...') and no Authorization header. Once a user enables API auth, the built-in API-key status/save/remove flows start returning 401, so users can no longer manage Anthropic keys from the UI.
Useful? React with 👍 / 👎.
| const token = getAuthToken(); | ||
| if (token && url.pathname.startsWith('/api/') && !url.pathname.startsWith('/api/auth/')) { | ||
| const authHeader = String(req.headers.authorization || ''); | ||
| const provided = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : ''; |
There was a problem hiding this comment.
Accept case-insensitive Bearer auth scheme
The token parser only accepts headers starting with exact 'Bearer ' casing. HTTP auth schemes are case-insensitive, so valid headers like authorization: bearer <token> are rejected with 401 even when the token is correct, creating avoidable client interoperability failures.
Useful? React with 👍 / 👎.
Jeremy8776
left a comment
There was a problem hiding this comment.
Review
Thanks for the work here, James. This adds substantial value — the test coverage and API auth are both overdue. A few things to address before merging.
1. Priority model: preference / style → soft loses semantics
migrateRules() concatenates the old preference and style fields into the single new soft field. Those were distinct concepts — one was "prefer this but not required", the other "stylistic guidance". Concatenating them discards that distinction. Either preserve them as separate fields or document this as an intentional simplification.
2. This PR is doing too much
Three distinct concerns in one PR:
- Rules priority model refactor (breaking data change)
- 12 test suites + API endpoint tests
- API auth + security fixes
- Projects publish UI + grid/list toggle
- Nav reorder (Rules before Skills)
Please split at minimum the rules model change from everything else. A data model migration deserves its own focused review cycle.
3. Third commit message is too vague
Some security related updates doesn't describe what changed or why. Please expand.
4. CSS coupling: Projects reuses .handoffs-list / .handoff-card
Projects tab markup (ui/projects.js) relies on CSS selectors from tab-handoffs.css. If handoffs styling is ever refactored, projects will break silently. Either rename/copy the selectors or import a shared base class.
5. docs/how-it-works.md — unrelated addition
A 270-line doc file is included with no clear connection to any of these changes. Does it belong here?
None of these are blockers individually, but together they make the diff harder to review and risk regressions. Happy to re-review once addressed.
59c6a52 to
aca6648
Compare
… general, preference-only for soul
Data model change:
- rules.json: coding/general are now {hard, preference, style} objects
instead of flat strings; soul is now {preference} instead of string
- Backward compatible: server accepts both old flat-string and new
priority-object formats; legacy data is auto-migrated on read
Server changes:
- server/lib/validation.js: validateRules accepts nested priority
objects with section-specific allowed priorities
- server/compiler.js: normalizeRules() converts legacy flat strings
to {preference: 'text'} for backward compatibility; all adapters
use flattenSection/flattenSectionLabeled helpers
UI changes:
- rules-lab.js: removed single-priority dropdown per section; each
coding/general card now has 3 labeled textareas (Hard rule,
Preference, Style guidance); soul has a single Preference textarea
- config.js: save/load reads/writes nested priority objects; handles
both legacy and new formats via migrateRules()
- store.js: RS.get() and RS.loadFromServer() auto-migrate legacy
flat-string data to priority-object format
- data.js: DEFAULT_RULES updated to nested format
- tab-config.css: added .rules-priority-group, .rules-priority-section,
.rules-priority-label styles
Tests:
- validation-smoke.js: added 8 new tests for priority-object validation,
including soul rejecting non-preference priorities, mixed flat/object
formats, and array values
…rvation in migrateRules
81deb20 to
e5d5101
Compare
Priority-based rules sections (hard/soft) replacing the flat-string model.
Model
preference/stylesub-fields merge intosoftwith## Preference/## StyleheadersServer
validateRulesaccepts onlyhard/softper sectionnormalizeRules()converts both legacy formats to canonical;flattenSection()helpers render per-adapterUI
Tests