fix: harden SAC security per audit (0.4.15)
Close audit findings: anti-spoof client IP for login rate limit, JWT/CORS enforce on startup, SSH host-key verification and sudo via stdin, optional WinRM HTTPS, SSE httpOnly cookie auth, ingest body limit, ILIKE escaping, and HMAC API key hashing with legacy SHA-256 fallback.
This commit is contained in:
+10
-2
@@ -1,4 +1,4 @@
|
||||
const TOKEN_KEY = "sac_token";
|
||||
const TOKEN_KEY = "sac_token";
|
||||
const ROLE_KEY = "sac_role";
|
||||
|
||||
export function getToken(): string | null {
|
||||
@@ -31,6 +31,14 @@ export function clearToken(): void {
|
||||
localStorage.removeItem(ROLE_KEY);
|
||||
}
|
||||
|
||||
export async function logoutApi(): Promise<void> {
|
||||
try {
|
||||
await fetch("/api/v1/auth/logout", { method: "POST", credentials: "same-origin" });
|
||||
} catch {
|
||||
/* ignore network errors on logout */
|
||||
}
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number;
|
||||
|
||||
@@ -55,7 +63,7 @@ export async function apiFetch<T>(path: string, init: RequestInit = {}): Promise
|
||||
if (token) {
|
||||
headers.set("Authorization", `Bearer ${token}`);
|
||||
}
|
||||
const res = await fetch(path, { ...init, headers });
|
||||
const res = await fetch(path, { ...init, headers, credentials: "same-origin" });
|
||||
const text = await res.text();
|
||||
if (res.status === 401) {
|
||||
if (hadToken) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<aside class="sac-sidebar">
|
||||
<div class="sac-sidebar-brand">
|
||||
<img src="/sac-icon.png" alt="" class="sac-brand-logo" width="40" height="40" />
|
||||
@@ -42,7 +42,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { clearToken, isAdmin } from "../api";
|
||||
import { clearToken, isAdmin, logoutApi } from "../api";
|
||||
import SidebarSystemStats from "./SidebarSystemStats.vue";
|
||||
import { useSacVersion } from "../composables/useSacVersion";
|
||||
import {
|
||||
@@ -82,8 +82,10 @@ function isActive(item: (typeof navItems.value)[number]) {
|
||||
}
|
||||
|
||||
function logout() {
|
||||
clearToken();
|
||||
router.push("/login");
|
||||
void logoutApi().finally(() => {
|
||||
clearToken();
|
||||
router.push("/login");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
import { getToken } from "../api";
|
||||
|
||||
export interface SacDashboardStreamMessage {
|
||||
@@ -41,7 +41,7 @@ export function useSacLiveEventStream(onNewEvent: () => void) {
|
||||
if (!token) return;
|
||||
|
||||
eventSource?.close();
|
||||
const url = `/api/v1/stream/events?token=${encodeURIComponent(token)}`;
|
||||
const url = `/api/v1/stream/events`;
|
||||
eventSource = new EventSource(url);
|
||||
|
||||
eventSource.onopen = () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */
|
||||
/** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */
|
||||
export const APP_NAME = "Security Alert Center";
|
||||
export const APP_VERSION = "0.4.11";
|
||||
export const APP_VERSION = "0.4.15";
|
||||
export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="overview-page">
|
||||
<h1>Обзор</h1>
|
||||
|
||||
@@ -556,7 +556,7 @@ function connectLive() {
|
||||
|
||||
eventSource?.close();
|
||||
|
||||
const url = `/api/v1/stream/events?token=${encodeURIComponent(token)}`;
|
||||
const url = `/api/v1/stream/events`;
|
||||
|
||||
eventSource = new EventSource(url);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user