feat: AI security controls + PostgreSQL hardening (Week 1 Quick Wins)

AI Security (EGAI 4.3.1.3, 4.3.1.4, 4.1.3.1, IAAI 3.6.26):
- AI Disclaimer banner in ChatPanel: "AI responses may be inaccurate"
- "AI Generated" violet badge on: chat messages, AI summaries,
  project narratives, AI-generated cover images
- HITL: system prompt now requires explicit user confirmation
  before any data mutation (strongly worded instruction)
- Mutation tool audit logging: all 31 write tools logged with
  tool name, params, userId, userRole via Pino

PostgreSQL Hardening (PG Standard V1.6):
- Audit logging: log_connections, log_disconnections, log_statement=ddl,
  log_min_duration_statement=1000 in docker-compose
- SUPERUSER removal script: scripts/harden-postgres.sh
  (NOSUPERUSER + minimal GRANT for app user)
- Health check: pg_isready -U capakraken -d capakraken
- Documentation: security-architecture.md Section 12 updated

Controls closed: EGAI 4.1.3.1, 4.3.1.3, 4.3.1.4, PG 3.3, 3.5

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-27 16:18:35 +01:00
parent 3f76211955
commit 1fc1e9f24c
11 changed files with 126 additions and 10 deletions
@@ -22,6 +22,7 @@ export function CoverArtSection({ projectId, coverImageUrl, coverFocusY = 50, pr
const [customPrompt, setCustomPrompt] = useState("");
const [showPromptInput, setShowPromptInput] = useState(false);
const [showFocusSlider, setShowFocusSlider] = useState(false);
const [isAiGenerated, setIsAiGenerated] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const utils = trpc.useUtils();
@@ -40,6 +41,7 @@ export function CoverArtSection({ projectId, coverImageUrl, coverFocusY = 50, pr
...(customPrompt.trim() ? { prompt: customPrompt.trim() } : {}),
});
setImageUrl(result.coverImageUrl);
setIsAiGenerated(true);
setShowPromptInput(false);
setCustomPrompt("");
void utils.project.getById.invalidate({ id: projectId });
@@ -165,6 +167,15 @@ export function CoverArtSection({ projectId, coverImageUrl, coverFocusY = 50, pr
/>
{/* Gradient overlay at bottom for readability */}
<div className="absolute inset-x-0 bottom-0 h-16 bg-gradient-to-t from-black/40 to-transparent" />
{/* AI Generated badge (EGAI 4.3.1.3) */}
{isAiGenerated && (
<span className="absolute left-3 top-3 inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] font-medium rounded bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-300 shadow-sm">
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
AI Generated
</span>
)}
</div>
) : (
<div