| S-01 |
Spoofing |
router/index.js — lazy route resolution |
mitigate |
Vue Router 4 beforeEach is async and fully resolves before any component is loaded or mounted. Guard at line 93 attempts authStore.refresh() before allowing any non-public route. Lazy loading (Plan 11-02) does not change guard sequencing. Verified: router guard tests in router.guard.test.js mock all lazy components and confirm guard fires before resolution. |
CLOSED |
| S-02 |
Spoofing |
App.vue — hamburger header renders before route settles |
mitigate |
The hamburger header is inside the v-else branch (line 4 of App.vue template), which only renders after route.meta.layout !== 'auth' AND !route.matched.some(r => r.meta.requiresAdmin). An unauthenticated navigation is redirected to /login before the component tree mounts. Header reveals no session-identifying data; it shows only the app name. |
CLOSED |
| S-03 |
Spoofing |
AdminLayout.vue — admin drawer implies admin role |
mitigate |
AdminLayout.vue is only reachable via the /admin route subtree (router index.js line 47). That route has meta: { requiresAdmin: true } (line 48). The guard at lines 104-110 confirms authStore.user?.role === 'admin' via to.matched.some(); non-admins are redirected to /. Tested in router.guard.test.js lines 115-135 (child route inheritance). |
CLOSED |
| T-01 |
Tampering |
All Phase 11 templates — content injection via v-html |
mitigate |
Grep over all 10 Phase 11 implementation files finds zero uses of v-html, innerHTML, or eval. All user-data interpolation uses Vue template double-brace syntax ({{ }}), which auto-escapes. Folder names, file names, error messages, and share handles are all rendered via text interpolation only. |
CLOSED |
| T-02 |
Tampering |
StorageBrowser.vue — drag-and-drop DOM manipulation |
accept |
The drag-and-drop implementation uses native dataTransfer.setData('text/plain', file.id) (line 349). The received fileId on drop is a UUID read from the in-memory file object, not from dataTransfer.getData(). No untrusted DOM string is used to construct API calls. Accepted: the drag event does not introduce a new injection surface. |
CLOSED |
| R-01 |
Repudiation |
All Phase 11 changes |
accept |
Phase 11 introduces no event logging, audit trail, or backend changes. Repudiation threats are not applicable to pure CSS/layout frontend changes. No user actions introduced in this phase bypass existing backend audit logging (all document/folder mutations still go through the authenticated API). |
CLOSED |
| I-01 |
Information Disclosure |
StorageBrowser.vue — CSS-hidden responsive columns |
accept |
The hidden sm:block / hidden md:block classes (lines 41-42, 97-98, 144-145) hide "Size" and "Modified" metadata columns on narrow viewports. These values are already rendered in the DOM and inspectable via browser DevTools. They contain file size bytes and ISO dates — no access-controlled content, PII, or credentials. All data in this component is already visible in the authenticated user's own file list. Risk accepted: CSS column hiding is a display optimization, not a security boundary. |
CLOSED |
| I-02 |
Information Disclosure |
vite.config.js — stats.html bundle analyzer output |
mitigate |
The visualizer is conditionally activated only when ANALYZE=true is set (strict string equality at line 8 of vite.config.js). frontend/stats.html is listed in .gitignore (confirmed at line 8 of root .gitignore). frontend/dist/ is also gitignored (line 6). The output file is placed in the project root (not dist/), so it cannot be served by the production Vite dev server or shipped in the build artifact. |
CLOSED |
| I-03 |
Information Disclosure |
AppSidebar.vue — admin navigation link visible to non-admins via DOM |
accept |
The Admin link in AppSidebar.vue is conditionally rendered with v-if="authStore.user?.role === 'admin'" (line 169). v-if removes the DOM node entirely for non-admin users — it is not CSS-hidden. Even if it were CSS-hidden, the link itself only navigates to /admin, which the router guard blocks. No admin data or content is exposed. |
CLOSED |
| I-04 |
Information Disclosure |
DocumentPreviewModal.vue — document content in iframe |
mitigate |
Document content is fetched via fetchDocumentContent(docId) using the project's authenticated API client (bearer token injected per client.js). The response blob is wrapped in URL.createObjectURL() and assigned to a sandboxed <iframe> as the src. No raw API URL with credentials is ever placed in the iframe src. Blob URLs are revoked on modal close (onUnmounted, line 131). |
CLOSED |
| D-01 |
Denial of Service |
router/index.js — lazy-load chunk fetch failure |
accept |
If a lazy route chunk fails to load (network error, CDN outage), Vue Router raises a NavigationFailure. The application does not crash; the user remains on the current route. No error recovery handler was added in Phase 11 — this matches pre-existing behavior. Accepted: chunk load failure is an infrastructure concern outside frontend scope; a future phase may add a global router.onError handler. This is not a regression introduced by Phase 11. |
CLOSED |
| D-02 |
Denial of Service |
App.vue / AdminLayout.vue — drawer stuck open blocking navigation |
mitigate |
drawerOpen is set to false in a watch(() => route.fullPath, ...) handler (App.vue line 64-66; AdminLayout.vue line 57-59). Any successful navigation closes the drawer. The backdrop click (@click="drawerOpen = false") provides a secondary close path. The drawer is CSS translate-x based — it never removes underlying DOM elements, so keyboard navigation and router-links behind the drawer remain accessible. |
CLOSED |
| E-01 |
Elevation of Privilege |
router/index.js + AdminLayout.vue — regular user reaching admin UI |
mitigate |
Three independent layers enforce admin-only access: (1) Router guard beforeEach checks to.matched.some(r => r.meta.requiresAdmin) and authStore.user?.role === 'admin' before allowing navigation (lines 104-110). (2) AdminLayout.vue is only registered as the component for the /admin route subtree. (3) AppSidebar.vue renders the Admin link only when role === 'admin' (v-if removes it from DOM). Privilege elevation via the admin drawer requires passing the router guard, which verifies the role claim from the server-issued JWT. |
CLOSED |
| E-02 |
Elevation of Privilege |
App.vue — admin user accessing regular user drawer |
accept |
The router guard (lines 112-115) redirects admin users away from non-admin, non-public routes to /admin. If an admin reaches the regular layout branch (e.g. visiting / directly), the v-else branch of App.vue renders AppSidebar, which includes an Admin link. This is the intended behavior — admins are separated by redirect, not by blocking the regular layout entirely. Accepted: the regular layout is not a security boundary for admins; the admin role check in the guard is. |
CLOSED |