Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
277 changes: 277 additions & 0 deletions BLOG.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse

from .routers import agents, translate, settings, global_skills, status, versions, variables, templates, blueprints, agent_changes, search
from .routers import agents, translate, settings, global_skills, status, versions, variables, templates, blueprints, agent_changes, search, security
from .services import version_db

# CORS origins — defaults to ["*"] for development.
Expand Down Expand Up @@ -49,6 +49,7 @@ async def lifespan(app: FastAPI):
app.include_router(blueprints.router, prefix="/api")
app.include_router(agent_changes.router, prefix="/api")
app.include_router(search.router, prefix="/api/search")
app.include_router(security.router, prefix="/api")

# Serve frontend static files (production build)
STATIC_DIR = Path(__file__).resolve().parent.parent.parent / "static"
Expand Down
12 changes: 12 additions & 0 deletions backend/app/routers/security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Read-only security audit API."""
from fastapi import APIRouter

from ..services.security_audit import build_audit_report

router = APIRouter(tags=["security"])


@router.get("/security/audit")
async def get_security_audit():
"""Aggregated read-only report: agents, skills, variables, env hints."""
return await build_audit_report()
1,595 changes: 1,595 additions & 0 deletions backend/app/services/security_audit.py

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions frontend/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ export function sendSessionMessage(agent, sessionKey, message, mode, envelopeCon
}).then(r => r.data)
}

// Security (read-only audit)
export const fetchSecurityAudit = () => api.get('/security/audit').then(r => r.data)

// Settings
export const fetchSettings = () => api.get('/settings').then(r => r.data)
export const updateSettings = (data) => api.put('/settings', data).then(r => r.data)
Expand Down
19 changes: 17 additions & 2 deletions frontend/src/components/AppLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@
<el-icon><Setting /></el-icon>
{{ t('management.title') }}
</router-link>
<router-link
to="/security"
class="nav-tab"
:class="{ active: isSecurity }"
>
<el-icon><Lock /></el-icon>
{{ t('app.security') }}
</router-link>
</nav>
</div>
<!-- Desktop right buttons -->
Expand Down Expand Up @@ -66,6 +74,10 @@
<el-icon><Setting /></el-icon>
{{ t('management.title') }}
</router-link>
<router-link to="/security" class="mobile-nav-item" :class="{ active: isSecurity }">
<el-icon><Lock /></el-icon>
{{ t('app.security') }}
</router-link>
<div class="mobile-nav-divider"></div>
<div class="mobile-nav-actions">
<el-button text class="lang-btn" @click="toggleLocale">
Expand All @@ -86,7 +98,7 @@
import { computed, ref, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Setting, DataLine, Monitor, Menu as MenuIcon, Close } from '@element-plus/icons-vue'
import { Setting, DataLine, Monitor, Menu as MenuIcon, Close, Lock } from '@element-plus/icons-vue'
import { useSettingsStore } from '../stores/settings'
import { useResponsive } from '../composables/useResponsive'

Expand All @@ -98,6 +110,7 @@ const { isMobile } = useResponsive()
const isDashboard = computed(() => route.path === '/dashboard')
const isAgents = computed(() => route.path.startsWith('/agents'))
const isManagement = computed(() => route.path.startsWith('/management'))
const isSecurity = computed(() => route.path.startsWith('/security'))

const mobileMenuOpen = ref(false)

Expand Down Expand Up @@ -184,7 +197,9 @@ function toggleLocale() {
}
.app-body {
flex: 1;
overflow: hidden;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
}
/* Mobile header */
.mobile-menu-btn {
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/BlueprintsPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -401,21 +401,22 @@
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { ref, computed, onMounted, onUnmounted, watch, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useBlueprintStore } from '../stores/blueprint'
import { useAgentStore } from '../stores/agent'
import { useVariableStore } from '../stores/variable'
import { fetchVariablesByScope, fetchBlueprintFileVersion } from '../api'
import CodeEditor from './CodeEditor.vue'
import DeriveAgentDialog from './DeriveAgentDialog.vue'
import BlueprintDiffView from './BlueprintDiffView.vue'
import VersionHistoryPanel from './VersionHistoryPanel.vue'
import VersionDiffView from './VersionDiffView.vue'
import VariableDialog from './VariableDialog.vue'
import { Search, Loading } from '@element-plus/icons-vue'

const CodeEditor = defineAsyncComponent(() => import('./CodeEditor.vue'))

const { t } = useI18n()
const store = useBlueprintStore()
const agentStore = useAgentStore()
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/FileViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
</template>

<script setup>
import { computed, ref, watch, nextTick } from 'vue'
import { computed, ref, watch, nextTick, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
import { Loading } from '@element-plus/icons-vue'
import hljs from 'highlight.js'
Expand All @@ -122,11 +122,12 @@ import { useTemplateStore } from '../stores/template'
import { fetchAgentVariables, fetchVersionDetail } from '../api'
import FileToolbar from './FileToolbar.vue'
import MarkdownRenderer from './MarkdownRenderer.vue'
import CodeEditor from './CodeEditor.vue'
import VersionHistoryPanel from './VersionHistoryPanel.vue'
import VersionDiffView from './VersionDiffView.vue'
import AgentVariablesDrawer from './AgentVariablesDrawer.vue'

const CodeEditor = defineAsyncComponent(() => import('./CodeEditor.vue'))

const { t } = useI18n()
const store = useAgentStore()
const templateStore = useTemplateStore()
Expand Down
Loading