# PRD: NowPage Infrastructure Hardening — 17 Issues, 4 Sprints — HC v1.3.3 > Source: https://ideas.asapai.net/prd-nowpage-issues-v1 > Type: framework | ID: prd-nowpage-issues-v1 --- ## hc-metadata { "hc_version": "1.3.3", "hc_type": "framework", "artifact_id": "prd-nowpage-issues-v1", "title": "PRD: NowPage Infrastructure Hardening — 17 Issues, 4 Sprints", "summary": "Product Requirements Document for fixing 17 issues found during deep infrastructure review. Organized into 4 sprints: critical security, high-priority reliability, medium quality, and low-priority hardening. Each issue has exact file/line, fix spec, rollback plan, acceptance criteria, and test commands.", "tags": [ "prd", "issues", "security", "infrastructure", "engineering", "sprint" ], "category": "engineering", "subcategory": "product-requirements", "created": "2026-02-20", "version": "1.0.0", "registry_stack": [ "https://ideas.asapai.net/meta-registry" ], "sprints": [ { "id": "S1", "name": "Critical Security & Data Integrity", "issues": [ "C1", "C2", "C3", "C4" ], "estimate": "1-2 hours" }, { "id": "S2", "name": "High-Priority Reliability", "issues": [ "H1", "H2", "H3", "H4", "H5", "H6" ], "estimate": "2-3 hours" }, { "id": "S3", "name": "Medium Quality & Safety", "issues": [ "M1", "M2", "M3", "M4", "M5", "M6", "M7" ], "estimate": "2-3 hours" }, { "id": "S4", "name": "Low-Priority Hardening", "issues": [ "L1", "L2", "L3", "L4", "L5", "L6", "L7" ], "estimate": "3-4 hours" } ] } --- ## hc-instructions ## PRD: NowPage Infrastructure Hardening This is a machine-readable Product Requirements Document for Claude Code autonomous execution. ### How to Use This PRD 1. **Read this page** via `get_page_html` or `?format=raw` to get the full structured spec 2. **Parse the hc-context-public block** — it contains every issue as a structured JSON object with: - `id`, `severity`, `file`, `line`, `title`, `problem`, `fix_spec`, `rollback`, `acceptance_criteria`, `test_command` 3. **Execute sprints in order**: S1 (critical) → S2 (high) → S3 (medium) → S4 (low) 4. **For each issue**: a. Read the target file at the specified line b. Apply the fix described in `fix_spec` c. Run the `test_command` to verify d. Check `acceptance_criteria` passes e. Commit with message referencing the issue ID 5. **After each sprint**: Run the full test suite (`node scripts/test-publish-api.js`) 6. **Rollback**: Each issue has a `rollback` field. If a fix breaks something, revert that specific commit. ### Safety Rules - ALWAYS commit before starting a fix (snapshot for rollback) - NEVER change function signatures unless the fix_spec explicitly says to - Run `npx tsc --noEmit` after each file change to catch type errors - Run tests after each sprint, not after each individual fix - If a fix touches a registry template pattern, verify with a dry-run publish first ### Ralph Loop Pattern For autonomous execution, use this loop per sprint: 1. Read all issues in the sprint from hc-context-public 2. For each issue: read file → apply fix → type-check 3. After all fixes in sprint: run test suite 4. If all pass: commit sprint 5. If any fail: identify which fix broke it, revert that one, re-test --- ## hc-context-public { "prd_version": "1.0.0", "project_root": "C:/Users/jason/Downloads/folio-saas", "test_command": "NP_API_KEY=np_live_324d8705cd1e3d8c44ee83d414863e7a0862f9b02daaac35421e5b74009846d1 node scripts/test-publish-api.js", "type_check_command": "npx tsc --noEmit", "pre_fix_protocol": [ "git add -A && git stash push -m 'pre-fix-snapshot'", "Read the target file at the specified line range", "Understand the surrounding code context (±20 lines)", "Apply the minimal fix described in fix_spec", "Run: npx tsc --noEmit" ], "post_sprint_protocol": [ "Run full test suite", "If pass: git add && git commit", "If fail: identify breaking fix, git checkout -- , re-test", "Publish updated issue tracker page with status updates" ], "sprints": [ { "id": "S1", "name": "Critical Security & Data Integrity", "description": "These 4 issues can cause security holes, data corruption, or 500 errors. Fix first.", "issues": ["C1", "C2", "C3", "C4"] }, { "id": "S2", "name": "High-Priority Reliability", "description": "These 6 issues cause incorrect behavior, race conditions, or confusing errors.", "issues": ["H1", "H2", "H3", "H4", "H5", "H6"] }, { "id": "S3", "name": "Medium Quality & Safety", "description": "These 7 issues limit functionality or create edge-case bugs. Lower urgency but improve robustness.", "issues": ["M1", "M2", "M3", "M4", "M5", "M6", "M7"] }, { "id": "S4", "name": "Low-Priority Hardening", "description": "Future improvements for performance, safety limits, and operational maturity.", "issues": ["L1", "L2", "L3", "L4", "L5", "L6", "L7"] } ], "issues": [ { "id": "C1", "severity": "critical", "sprint": "S1", "title": "Permissive auth defaults — empty scopes grant full access", "file": "lib/auth/api-key.ts", "lines": "59-70", "problem": "hasAccessToDomain() returns true when domain_ids is empty/null. hasPermission() returns true when permissions is empty/null. This means any API key created with default empty scopes has unrestricted access to all domains and all operations. If a key is leaked, attacker has god-mode.", "current_code": "// line 61\nif (!keyData.domain_ids || keyData.domain_ids.length === 0) return true\n// line 68\nif (!keyData.permissions || Object.keys(keyData.permissions).length === 0) return true", "fix_spec": "This is an intentional design choice for convenience (documented in POST /api/keys — empty means 'all'). The real fix is defense-in-depth: 1) Add a comment explicitly documenting this behavior. 2) In validateApiKey(), when constructing the return object, explicitly set domain_ids to [] and permissions to {} if null (normalize). 3) Add a log warning when a key with empty scopes is used: console.warn('API key used with unrestricted scope:', keyData.key_id). 4) In the keys creation endpoint (POST /api/keys), add a response field 'scope_warning' if both are empty.", "rollback": "git checkout -- lib/auth/api-key.ts app/api/keys/route.ts", "acceptance_criteria": [ "Existing keys with empty scopes still work (backwards compatible)", "New keys created with empty scopes get a scope_warning in response", "Console logs show a warning when unrestricted key is used", "Test suite still passes (31/31)" ], "test_command": "NP_API_KEY=np_live_324d8705cd1e3d8c44ee83d414863e7a0862f9b02daaac35421e5b74009846d1 node scripts/test-publish-api.js", "touches_files": ["lib/auth/api-key.ts", "app/api/keys/route.ts"] }, { "id": "C2", "severity": "critical", "sprint": "S1", "title": "JSON parse failure returns wrong regex match group — silent data corruption", "file": "lib/publish/core.ts", "lines": "384-386", "problem": "In updateRegistryMetadata(), the catch block on line 384-385 returns `_` which is the captured content group (the JSON text inside the script tag), not the full match including the