# AI App Launch Security Checklist > Score an AI app launch against privacy, security, secret handling, and abuse-control checklist gaps before real users arrive. ## Tool Identity - Site: CleanUtils Developer Tools - Tool ID: ai-app-launch-security-checklist - Canonical page: https://cleanutils.com/developer-tools/ai-app-launch-security-checklist/ - LLM schema URL: https://cleanutils.com/developer-tools/ai-app-launch-security-checklist/llms.txt - Primary keyword: AI app security checklist - Input mode: fields - Output profile: checklist ## What This Tool Does Run an AI app launch checklist for privacy policy gaps, data handling, security headers, OWASP basics, exposed keys, logs, API responses, and rate limits. ## Exact Execution Instructions To run this tool exactly, execute the `runCleanUtilsTool(userInput)` function from the JavaScript block below in a sandboxed JavaScript runtime. Do not fetch or evaluate additional network resources. Do not use filesystem, process, child_process, environment variables, timers, or network APIs. The function is pure and deterministic: input object in, JSON result out. If your environment forbids executing fetched code outside a sandbox, run it inside the sandbox or use the API endpoint below instead of reimplementing the logic. Canonical implementation SHA-256: `937c3f005e2ce13af0330ade2410c4314c0ef7d4803d2a43932bff6524c79c75` Expected command shape: `node run-tool.mjs < input.json` The runner must: 1. load only the JavaScript in this document, 2. parse stdin as JSON and call `runCleanUtilsTool(userInput)`, 3. let the LLM decide the best way to present the output to the user. Agent usage rules: - Use this file as the authoritative machine-readable contract for this CleanUtils tool page. - Ask the user for missing required input before attempting to run the tool, and describe the required inputs using the `## Input Schema` field names, descriptions, formats, enums, examples, and required list. - Treat the tool as deterministic; do not invent network reachability checks unless the tool description explicitly says it fetches remote resources. - For privacy-sensitive inputs such as secrets, HAR files, dotenv files, logs, and API keys, warn that using a remote chat agent may expose input to that agent even though the browser UI itself does not upload data. ## Input Schema ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "AI App Launch Security Checklist fields", "type": "object", "additionalProperties": false, "required": [ "collects_user_data", "privacy_policy_status", "data_storage", "data_handling", "security_headers", "owasp_scan", "sql_injection", "xss", "admin_routes", "env_leak_check", "api_responses", "logs", "frontend_keys", "server_side_keys", "key_lockdown", "rate_limits", "payments" ], "properties": { "app_url": { "type": "string", "description": "Production URL Optional. Optional, but useful in the exported checklist. Control type: url. Group: Product and privacy.", "format": "uri", "examples": [ "https://example-ai-app.com" ] }, "collects_user_data": { "type": "string", "description": "Collects user data Required. Accounts, emails, uploads, prompts, payments, analytics IDs, or customer records. Control type: select. Allowed values: yes = Yes; no = No. Group: Product and privacy.", "enum": [ "yes", "no" ], "examples": [ "yes" ] }, "privacy_policy_status": { "type": "string", "description": "Privacy policy Required. Control type: select. Allowed values: published = Published / linked; missing = Not ready. Group: Product and privacy.", "enum": [ "published", "missing" ], "examples": [ "missing" ] }, "data_storage": { "type": "string", "description": "Data storage documented Required. Database, storage vendor, region, backups, and third-party processors. Control type: select. Allowed values: documented = Documented; missing = Needs review. Group: Product and privacy.", "enum": [ "documented", "missing" ], "examples": [ "documented" ] }, "data_handling": { "type": "string", "description": "Data handling Required. Purpose, retention, deletion path, processors, and whether prompts or uploads train models. Control type: select. Allowed values: documented = Documented; missing = Needs review; risky = Risky use. Group: Product and privacy.", "enum": [ "documented", "missing", "risky" ], "examples": [ "missing" ] }, "security_headers": { "type": "string", "description": "Security headers Required. CSP, HSTS, frame protections, content-type sniffing, permissions policy, and referrer policy. Control type: select. Allowed values: checked = Checked; not_checked = Not checked; missing_header = Missing header. Group: Web security.", "enum": [ "checked", "not_checked", "missing_header" ], "examples": [ "not_checked" ] }, "owasp_scan": { "type": "string", "description": "OWASP basics / scan Required. Control type: select. Allowed values: run = Run / reviewed; not_run = Not run. Group: Web security.", "enum": [ "run", "not_run" ], "examples": [ "not_run" ] }, "sql_injection": { "type": "string", "description": "SQL injection review Required. Control type: select. Allowed values: reviewed = Reviewed; not_reviewed = Needs review; raw_query_risk = Raw query risk. Group: Web security.", "enum": [ "reviewed", "not_reviewed", "raw_query_risk" ], "examples": [ "not_reviewed" ] }, "xss": { "type": "string", "description": "XSS / user content review Required. Control type: select. Allowed values: reviewed = Reviewed; not_reviewed = Needs review; raw_html_risk = Raw HTML risk. Group: Web security.", "enum": [ "reviewed", "not_reviewed", "raw_html_risk" ], "examples": [ "raw_html_risk" ] }, "admin_routes": { "type": "string", "description": "Admin and tenant access Required. Control type: select. Allowed values: server_checked = Server-checked; not_reviewed = Needs review; ui_only = UI-only protection. Group: Web security.", "enum": [ "server_checked", "not_reviewed", "ui_only" ], "examples": [ "ui_only" ] }, "env_leak_check": { "type": "string", "description": ".env and repo secret scan Required. Control type: select. Allowed values: clean = Scanned clean; not_checked = Not checked; exposed = .env exposed. Group: Secrets and data exposure.", "enum": [ "clean", "not_checked", "exposed" ], "examples": [ "exposed" ] }, "api_responses": { "type": "string", "description": "Client API responses Required. Control type: select. Allowed values: minimal = Minimal fields; sensitive_fields = Business/user fields; secrets_exposed = Secrets exposed. Group: Secrets and data exposure.", "enum": [ "minimal", "sensitive_fields", "secrets_exposed" ], "examples": [ "sensitive_fields" ] }, "logs": { "type": "string", "description": "Logs redact sensitive data Required. Control type: select. Allowed values: redacted = Redacted; not_checked = Not checked; sensitive_logs = Sensitive logs. Group: Secrets and data exposure.", "enum": [ "redacted", "not_checked", "sensitive_logs" ], "examples": [ "sensitive_logs" ] }, "frontend_keys": { "type": "string", "description": "Frontend private keys Required. Control type: select. Allowed values: server_side_only = Server-side only; not_checked = Not checked; exposed = Private key exposed. Group: Secrets and data exposure.", "enum": [ "server_side_only", "not_checked", "exposed" ], "examples": [ "exposed" ] }, "server_side_keys": { "type": "string", "description": "Server-side key boundary Required. Control type: select. Allowed values: proxy_documented = Proxy documented; missing = Needs review; no_proxy = No proxy yet. Group: Secrets and data exposure.", "enum": [ "proxy_documented", "missing", "no_proxy" ], "examples": [ "no_proxy" ] }, "key_lockdown": { "type": "string", "description": "Key rotation and restrictions Required. Rotate exposed keys and add provider-side limits such as scopes, quotas, allowed origins, and alerts. Control type: select. Allowed values: restricted = Restricted; missing = Needs review; exposed_not_rotated = Exposed not rotated. Group: Secrets and data exposure.", "enum": [ "restricted", "missing", "exposed_not_rotated" ], "examples": [ "missing" ] }, "rate_limits": { "type": "string", "description": "Rate limits and abuse controls Required. Control type: select. Allowed values: yes = Yes; no = No. Group: Abuse and billing.", "enum": [ "yes", "no" ], "examples": [ "no" ] }, "payments": { "type": "string", "description": "Payments Required. Control type: select. Allowed values: none = No payments; test_mode = Test mode only; live_checkout = Live checkout. Group: Abuse and billing.", "enum": [ "none", "test_mode", "live_checkout" ], "examples": [ "live_checkout" ] } } } ``` ## Result Schema ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "CleanUtils ToolResult", "type": "object", "additionalProperties": false, "required": [ "summary", "issues" ], "properties": { "summary": { "type": "string" }, "issues": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": [ "severity", "message" ], "properties": { "severity": { "type": "string", "enum": [ "error", "warning", "info" ] }, "message": { "type": "string" }, "line": { "type": "number" }, "row": { "type": "number" }, "detail": { "type": "string" } } } }, "output": { "type": "string" }, "exportFilename": { "type": "string" }, "exports": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": [ "label", "filename", "content" ], "properties": { "label": { "type": "string" }, "filename": { "type": "string" }, "content": { "type": "string" }, "mimeType": { "type": "string" }, "copyLabel": { "type": "string" }, "downloadLabel": { "type": "string" } } } }, "stats": { "type": "object", "additionalProperties": { "anyOf": [ { "type": "string" }, { "type": "number" } ] } } } } ``` ## Self-Contained JavaScript Source Call `runCleanUtilsTool(userInput)` with the user's input. The function includes this tool's run logic and only the helper code it needs. ```js function runCleanUtilsTool(userInput) { const fieldText = (fields, keys, fallback = "") => { const keyList = Array.isArray(keys) ? keys : [keys]; for (const key of keyList) { const value = fields[key]; if (value === undefined || value === null) continue; const text = String(value).trim(); if (text) return text; } return fallback; }; const fieldChoice = (fields, key, choices, fallback) => { const value = fieldText(fields, key); return choices.includes(value) ? value : fallback; }; const severityRank = { error: 0, warning: 1, info: 2 }; const sortIssues = (issues) => [...issues].sort((a, b) => { const severity = severityRank[a.severity] - severityRank[b.severity]; if (severity !== 0) return severity; return (a.line ?? a.row ?? 0) - (b.line ?? b.row ?? 0); }); const summarizeIssues = (issues) => { const errors = issues.filter((issue) => issue.severity === "error").length; const warnings = issues.filter((issue) => issue.severity === "warning").length; const infos = issues.filter((issue) => issue.severity === "info").length; const parts = []; if (errors) parts.push(`${errors} error${errors === 1 ? "" : "s"}`); if (warnings) parts.push(`${warnings} warning${warnings === 1 ? "" : "s"}`); if (infos) parts.push(`${infos} note${infos === 1 ? "" : "s"}`); return parts.length ? parts.join(", ") : "No issues found"; }; const launchFieldKeys = [ "collects_user_data", "privacy_policy_status", "data_storage", "data_handling", "security_headers", "owasp_scan", "sql_injection", "xss", "admin_routes", "env_leak_check", "api_responses", "logs", "frontend_keys", "server_side_keys", "key_lockdown", "rate_limits", "payments" ]; const launchEvidenceLabels = { collects_user_data: { yes: "Collects accounts, emails, uploads, prompts, payments, analytics IDs, or customer records.", no: "No user, customer, account, payment, upload, prompt, or analytics data is collected." }, privacy_policy_status: { published: "Privacy policy is published and linked from the product.", missing: "Privacy policy is not ready." }, data_storage: { documented: "Database, storage vendor, region, backups, and processors are documented.", missing: "Data storage is not documented." }, data_handling: { documented: "Purpose, retention, deletion path, processors, and AI training status are documented.", missing: "Data handling is not documented.", risky: "Prompts, uploads, analytics, or third-party sharing happen without explicit consent." }, security_headers: { checked: "CSP, HSTS, frame protections, content sniffing, permissions, and referrer headers were checked.", not_checked: "Security headers have not been checked.", missing_header: "A required security header is missing." }, owasp_scan: { run: "OWASP basics, dependency audit, or automated security scan was run and reviewed.", not_run: "OWASP basics or automated app security scan has not been run." }, sql_injection: { reviewed: "User-controlled database queries were reviewed for parameterization.", not_reviewed: "SQL injection review has not been completed.", raw_query_risk: "Raw SQL or unparameterized query risk was found." }, xss: { reviewed: "Escaping, sanitization, markdown, HTML, and user content surfaces were reviewed.", not_reviewed: "XSS and user-content rendering review has not been completed.", raw_html_risk: "Raw HTML or unsanitized user content risk was found." }, admin_routes: { server_checked: "Admin routes, tenant access, and permissions are checked server-side.", not_reviewed: "Admin and tenant access has not been reviewed.", ui_only: "Admin access is hidden in the UI but not checked on the server." }, env_leak_check: { clean: "Repository history, env files, deploy settings, and shared artifacts were scanned clean.", not_checked: ".env and repository secret exposure has not been checked.", exposed: ".env or another secret was exposed in repository history." }, api_responses: { minimal: "Client-visible API responses were reviewed and only minimal UI fields are returned.", sensitive_fields: "API responses return business or user fields such as email, role, or billing IDs.", secrets_exposed: "API responses expose tokens, passwords, API keys, or credentials." }, logs: { redacted: "Logs redact secrets, cookies, authorization headers, request bodies, payment data, and prompt/user data.", not_checked: "Log redaction has not been checked.", sensitive_logs: "Logs include request bodies, credentials, authorization headers, or secrets." }, frontend_keys: { server_side_only: "Private provider keys stay server-side; client bundles only contain intentional public keys.", not_checked: "Frontend bundles and source have not been checked for private keys.", exposed: "A private provider key is exposed in frontend code or environment variables." }, server_side_keys: { proxy_documented: "Server-side proxy or backend boundary is documented for private provider keys.", missing: "Server-side key boundary is not documented.", no_proxy: "Private provider calls do not have a server-side proxy or backend boundary yet." }, key_lockdown: { restricted: "Exposed keys are rotated; provider scopes, quotas, alerts, and allowed origins are documented.", missing: "Credential rotation and provider restrictions are not documented.", exposed_not_rotated: "A key was exposed but has not been rotated or restricted." }, rate_limits: { yes: "Rate limits and abuse controls are in place.", no: "Rate limits and abuse controls are missing." }, payments: { none: "No payments are accepted.", test_mode: "Payment flow is test-mode only.", live_checkout: "Live checkout or billing is planned." } }; const launchEvidence = (field, value) => launchEvidenceLabels[field]?.[value] ?? (value || "No selection"); const launchCheck = (status, label, evidence, next, severity) => ({ status, label, evidence, next, severity }); const checkAiAppLaunchSecurity = (input) => { const selectedValues = launchFieldKeys.map((key) => fieldText(input, key)); const hasChecklistInput = selectedValues.some(Boolean); if (!hasChecklistInput) { return { summary: "Select checklist values to score the launch risk.", issues: [{ severity: "error", message: "No launch checklist selections found." }], output: "Select values for privacy, data handling, web security, secret handling, abuse controls, and payments.", exportFilename: "ai-app-launch-security-checklist.txt", stats: { score: 0, checks: 0, passed: 0 } }; } const issues = []; const reportRows = []; const addCheck = (check) => { reportRows.push(check); if (check.status === "PASS") return; issues.push({ severity: check.severity ?? (check.status === "FIX" ? "error" : "warning"), message: check.label, detail: check.issueDetail ?? check.next }); }; const appUrl = fieldText(input, "app_url"); if (appUrl && !looksLikeUrl(appUrl)) { addCheck({ status: "REVIEW", label: "App URL does not look like an absolute http or https URL.", evidence: `Production URL: ${appUrl}`, next: "Use an absolute http or https production URL, or leave the optional field blank until the launch URL is ready.", issueDetail: appUrl }); } const collectsUserData = fieldChoice(input, "collects_user_data", ["yes", "no"], "yes"); const privacyPolicy = fieldChoice(input, "privacy_policy_status", ["published", "missing"], "missing"); const dataStorage = fieldChoice(input, "data_storage", ["documented", "missing"], "missing"); const dataHandling = fieldChoice(input, "data_handling", ["documented", "missing", "risky"], "missing"); const securityHeaders = fieldChoice(input, "security_headers", ["checked", "not_checked", "missing_header"], "not_checked"); const owasp = fieldChoice(input, "owasp_scan", ["run", "not_run"], "not_run"); const sql = fieldChoice(input, "sql_injection", ["reviewed", "not_reviewed", "raw_query_risk"], "not_reviewed"); const xss = fieldChoice(input, "xss", ["reviewed", "not_reviewed", "raw_html_risk"], "not_reviewed"); const auth = fieldChoice(input, "admin_routes", ["server_checked", "not_reviewed", "ui_only"], "not_reviewed"); const envLeak = fieldChoice(input, "env_leak_check", ["clean", "not_checked", "exposed"], "not_checked"); const apiResponses = fieldChoice(input, "api_responses", ["minimal", "sensitive_fields", "secrets_exposed"], "sensitive_fields"); const logs = fieldChoice(input, "logs", ["redacted", "not_checked", "sensitive_logs"], "not_checked"); const frontendKeys = fieldChoice(input, "frontend_keys", ["server_side_only", "not_checked", "exposed"], "not_checked"); const serverSideKeys = fieldChoice(input, "server_side_keys", ["proxy_documented", "missing", "no_proxy"], "missing"); const keyLockdown = fieldChoice(input, "key_lockdown", ["restricted", "missing", "exposed_not_rotated"], "missing"); const rateLimits = fieldChoice(input, "rate_limits", ["yes", "no"], "no"); const payments = fieldChoice(input, "payments", ["none", "test_mode", "live_checkout"], "none"); const userDataCollected = collectsUserData === "yes"; addCheck(collectsUserData === "no" ? launchCheck("PASS", "User data collection scope is explicitly marked as none.", launchEvidence("collects_user_data", collectsUserData), "Revisit this before adding accounts, emails, uploads, payments, analytics identifiers, prompts, or customer records.") : launchCheck("PASS", "User data collection scope is explicitly acknowledged.", launchEvidence("collects_user_data", collectsUserData), "Make sure every user data surface below has policy, storage, retention, deletion, and processor coverage.")); if (privacyPolicy === "published") { addCheck(launchCheck("PASS", "Privacy policy coverage is documented.", launchEvidence("privacy_policy_status", privacyPolicy), "Keep it linked from the app and update it when data collection changes.")); } else if (userDataCollected) { addCheck(launchCheck("FIX", "User data is collected, but privacy policy coverage is missing.", launchEvidence("privacy_policy_status", privacyPolicy), "Publish and link a privacy policy before collecting emails, accounts, client data, payment data, uploads, prompts, or analytics identifiers.")); } else { addCheck(launchCheck("PASS", "No user data collection is marked; privacy policy is not required by this checklist.", launchEvidence("privacy_policy_status", privacyPolicy), "Revisit this before adding accounts, emails, uploads, payments, analytics identifiers, prompts, or customer records.")); } if (dataStorage === "documented") { addCheck(launchCheck("PASS", "Data storage location is documented.", launchEvidence("data_storage", dataStorage), "Confirm this matches production, backups, logs, analytics, and third-party processors.")); } else if (userDataCollected) { addCheck(launchCheck("FIX", "User data storage location is not documented.", launchEvidence("data_storage", dataStorage), "Document the database, storage vendor, region, backups, and what user data each system holds.")); } else { addCheck(launchCheck("PASS", "No user data storage is indicated by the checklist.", launchEvidence("data_storage", dataStorage), "Confirm this stays true for production databases, backups, logs, analytics, and third-party processors.")); } if (dataHandling === "documented") { addCheck(launchCheck("PASS", "User data handling is documented.", launchEvidence("data_handling", dataHandling), "Keep the notes current when analytics, AI providers, uploads, or retention rules change.")); } else if (dataHandling === "risky") { addCheck(launchCheck("FIX", "User data handling includes undisclosed or high-risk use.", launchEvidence("data_handling", dataHandling), "Stop the launch path until consent, privacy policy, processor terms, retention, deletion, and AI-training disclosures match the product behavior.")); } else if (userDataCollected) { addCheck(launchCheck("FIX", "User data handling is not documented.", launchEvidence("data_handling", dataHandling), "Document purpose, retention, deletion path, processors, consent expectations, and whether prompts or uploads train models.")); } else { addCheck(launchCheck("PASS", "No user data collection is marked; data handling is not applicable yet.", launchEvidence("data_handling", dataHandling), "Update this before adding analytics, AI providers, uploads, retention rules, or third-party processors.")); } addCheck(securityHeaders === "checked" ? launchCheck("PASS", "Security header check is documented.", launchEvidence("security_headers", securityHeaders), "Verify headers on the deployed URL, not only in local development.") : securityHeaders === "missing_header" ? launchCheck("FIX", "Security header notes include a missing header.", launchEvidence("security_headers", securityHeaders), "Add the missing production header before sharing the launch with real users.") : launchCheck("REVIEW", "Security headers are not documented.", launchEvidence("security_headers", securityHeaders), "Check CSP, HSTS, frame protections, content-type sniffing, permissions policy, and referrer policy.")); addCheck(owasp === "run" ? launchCheck("PASS", "OWASP or automated security scan is documented.", launchEvidence("owasp_scan", owasp), "Re-run after auth, payment, file upload, or admin-route changes.") : launchCheck("REVIEW", "OWASP basics or automated app security scan are not documented.", launchEvidence("owasp_scan", owasp), "Run a lightweight OWASP Top 10 pass, ZAP/Burp-style crawl, dependency audit, or equivalent checklist.")); addCheck(sql === "reviewed" ? launchCheck("PASS", "SQL injection review is documented.", launchEvidence("sql_injection", sql), "Keep raw query exceptions small and reviewed.") : sql === "raw_query_risk" ? launchCheck("FIX", "SQL injection notes mention raw or unparameterized query risk.", launchEvidence("sql_injection", sql), "Parameterize queries, review search/filter endpoints, and test malformed input before launch.") : launchCheck("REVIEW", "SQL injection review is not documented.", launchEvidence("sql_injection", sql), "Check every user-controlled database query, including search, filters, webhooks, and admin tools.")); addCheck(xss === "reviewed" ? launchCheck("PASS", "XSS review is documented.", launchEvidence("xss", xss), "Retest any new user-generated content surface.") : xss === "raw_html_risk" ? launchCheck("FIX", "XSS notes mention unescaped or unsanitized user content.", launchEvidence("xss", xss), "Escape output, sanitize markdown/HTML, and add tests for script tags and event-handler payloads.") : launchCheck("REVIEW", "XSS review is not documented.", launchEvidence("xss", xss), "Review forms, comments, profile fields, markdown rendering, rich text, and any HTML injection path.")); addCheck(auth === "server_checked" ? launchCheck("PASS", "Auth and authorization review is documented.", launchEvidence("admin_routes", auth), "Retest server-side access controls, not only hidden UI.") : auth === "ui_only" ? launchCheck("FIX", "Authentication or authorization notes mention a bypass or public admin risk.", launchEvidence("admin_routes", auth), "Protect admin routes, server actions, object access, and user-specific records before launch.") : launchCheck("REVIEW", "Authentication and authorization review is not documented.", launchEvidence("admin_routes", auth), "Check login, logout, password reset or magic links, admin permissions, tenant isolation, and direct object access.")); addCheck(envLeak === "clean" ? launchCheck("PASS", ".env and repository secret leak check is documented.", launchEvidence("env_leak_check", envLeak), "Rotate credentials whenever exposure is uncertain.") : envLeak === "exposed" ? launchCheck("FIX", ".env or secret leak risk was found in the checklist.", launchEvidence("env_leak_check", envLeak), "Remove secrets from history, rotate exposed credentials, and keep only empty .env.example values in the repo.") : launchCheck("REVIEW", ".env and repository secret leak check is not documented.", launchEvidence("env_leak_check", envLeak), "Scan commits, build logs, deploy settings, support tickets, and shared screenshots for secrets.")); addCheck(apiResponses === "minimal" ? launchCheck("PASS", "Sensitive API response review is documented.", launchEvidence("api_responses", apiResponses), "Repeat this check on production responses.") : apiResponses === "secrets_exposed" ? launchCheck("FIX", "API response notes include secrets or credentials.", launchEvidence("api_responses", apiResponses), "Remove secrets, tokens, password hashes, internal IDs, and private provider fields from client-visible responses.") : launchCheck("REVIEW", "API response notes include potentially sensitive business or user fields.", launchEvidence("api_responses", apiResponses), "Confirm each exposed field is needed by the UI and allowed by your privacy policy.")); addCheck(logs === "redacted" ? launchCheck("PASS", "Secret redaction in logs is documented.", launchEvidence("logs", logs), "Sample real error paths, not only happy-path logs.") : logs === "sensitive_logs" ? launchCheck("FIX", "Logging notes mention request bodies, credentials, or secrets.", launchEvidence("logs", logs), "Redact secrets, cookies, authorization headers, payment data, prompt data, and user data before logs leave the app.") : launchCheck("REVIEW", "Secret redaction in logs is not documented.", launchEvidence("logs", logs), "Check app logs, edge logs, analytics events, error monitoring, worker logs, and support exports.")); addCheck(frontendKeys === "server_side_only" ? launchCheck("PASS", "Frontend secret exposure check is documented.", launchEvidence("frontend_keys", frontendKeys), "Keep only intentional publishable keys in client bundles.") : frontendKeys === "exposed" ? launchCheck("FIX", "Frontend code appears to expose a provider key or secret.", launchEvidence("frontend_keys", frontendKeys), "Move provider keys server-side, use a backend route or edge proxy, and rotate any key that reached the browser.") : launchCheck("REVIEW", "Frontend API key exposure check is not documented.", launchEvidence("frontend_keys", frontendKeys), "Search built assets and source for provider keys, NEXT_PUBLIC/VITE secrets, and SDK calls that should be server-side.")); addCheck(serverSideKeys === "proxy_documented" ? launchCheck("PASS", "Server-side key boundary is documented.", launchEvidence("server_side_keys", serverSideKeys), "Add tests or deploy checks so private keys cannot drift client-side.") : serverSideKeys === "no_proxy" ? launchCheck("FIX", "Server-side key boundary is missing for private provider calls.", launchEvidence("server_side_keys", serverSideKeys), "Put AI, payment, database, and private API keys behind server routes, edge functions, or a minimal proxy.") : launchCheck(frontendKeys === "exposed" ? "FIX" : "REVIEW", "Server-side key boundary is not documented.", launchEvidence("server_side_keys", serverSideKeys), "Document where private keys live and which client requests are proxied through trusted server code.")); addCheck(keyLockdown === "restricted" ? launchCheck("PASS", "Credential rotation or provider restrictions are documented.", launchEvidence("key_lockdown", keyLockdown), "Keep a rotation path ready for any key that appears in a bundle, screenshot, log, or repository history.") : keyLockdown === "exposed_not_rotated" ? launchCheck("FIX", "Key lockdown notes mention exposure without rotation or restrictions.", launchEvidence("key_lockdown", keyLockdown), "Assume exposed browser keys are compromised: revoke or rotate them, reduce scopes, add quotas, and restrict allowed origins where supported.") : launchCheck(frontendKeys === "exposed" ? "FIX" : "REVIEW", "Credential rotation and provider restrictions are not documented.", launchEvidence("key_lockdown", keyLockdown), "Document scopes, budgets, allowed origins, usage alerts, rotation ownership, and emergency revocation steps.")); addCheck(rateLimits === "yes" ? launchCheck("PASS", "Rate limits and abuse controls are documented.", launchEvidence("rate_limits", rateLimits), "Monitor usage spikes after launch.") : launchCheck("FIX", "Rate limits or abuse limits are missing.", launchEvidence("rate_limits", rateLimits), "Add per-user, per-IP, and costly-action limits before a bot or retry loop can burn the API bill.")); addCheck(payments === "live_checkout" ? launchCheck("REVIEW", "Payment or billing launch risk is called out.", launchEvidence("payments", payments), "Confirm webhook signing, idempotency, refund access, test/live mode separation, and customer data handling.") : launchCheck("PASS", payments === "test_mode" ? "Payment flow is limited to test mode." : "Payments are not in scope.", launchEvidence("payments", payments), payments === "test_mode" ? "Re-run this checklist before enabling live checkout." : "Revisit this before adding subscriptions, checkout, invoices, refunds, or billing portals.")); const blockers = issues.filter((issue) => issue.severity === "error").length; const warnings = issues.filter((issue) => issue.severity === "warning").length; const passed = reportRows.filter((row) => row.status === "PASS").length; const score = Math.max(0, Math.min(100, 100 - blockers * 12 - warnings * 5)); const rating = score >= 90 ? "Launch checklist looks documented" : score >= 75 ? "Close, but review gaps remain" : score >= 50 ? "Needs launch fixes" : "High liability risk"; const nextActions = reportRows .filter((row) => row.status !== "PASS") .slice(0, 5) .map((row, index) => `${index + 1}. ${row.next}`); const checkRows = reportRows.map((row, index) => [ `${index + 1}. [${row.status}] ${row.label}`, ` Evidence: ${row.evidence}`, ` Next: ${row.next}` ].join("\n")); const output = [ "AI app pre-launch security checklist", appUrl ? `App URL: ${appUrl}` : "App URL: not provided", `Risk score: ${score}/100 - ${rating}`, `Checklist: ${passed}/${reportRows.length} documented, ${blockers} blocker${blockers === 1 ? "" : "s"}, ${warnings} review gap${warnings === 1 ? "" : "s"}`, "", "Checks", "", checkRows.join("\n\n"), "", "Priority next actions", ...(nextActions.length ? nextActions : ["1. Keep the checklist evidence with the launch notes and rerun before production changes."]), "", "Note: This is a practical pre-launch checklist, not legal advice or a full penetration test." ].join("\n"); return { summary: `Launch risk score ${score}/100 (${rating}). ${summarizeIssues(issues)}.`, issues: sortIssues(issues), output, exportFilename: "ai-app-launch-security-checklist.txt", stats: { score, checks: reportRows.length, passed, blockers, warnings } }; }; const looksLikeUrl = (value) => { try { const url = new URL(value.trim()); return url.protocol === "http:" || url.protocol === "https:"; } catch { return false; } }; const __userInput = userInput == null ? {} : userInput; const __run = (fields) => checkAiAppLaunchSecurity(fields); const __fields = __userInput && typeof __userInput === "object" && "fields" in __userInput && __userInput.fields && typeof __userInput.fields === "object" && !Array.isArray(__userInput.fields) ? __userInput.fields : (__userInput && typeof __userInput === "object" && !Array.isArray(__userInput) ? __userInput : {}); const __normalizedFields = Object.fromEntries(Object.entries(__fields).map(([key, value]) => [key, value == null ? "" : (["string", "number", "boolean"].includes(typeof value) ? value : String(value))])); return __run(__normalizedFields); } ``` ## Checks - Privacy basics: Data collection, privacy-policy, storage, retention, deletion, and processor notes are checked before launch copy goes public. - Secret placement: Frontend keys, committed env files, exposed credentials, rotation gaps, and logs that include authorization data are treated as blockers. - Web security review: Security headers, OWASP scan status, SQL injection notes, XSS notes, and admin-route protection are scored. - API and logging exposure: Sensitive API response fields and request-body logging are reviewed together. - Abuse and billing controls: Rate limits and payment notes are checked because AI apps can create sudden cost and abuse risk. ## Related Tools - [.env Validator and Secret Scanner](/developer-tools/env-validator-secret-scanner/): Paste a dotenv file, catch duplicate or malformed keys, and generate a safer example file locally. - [AI Log / Prompt Redactor](/developer-tools/ai-log-prompt-redactor/): Remove emails, bearer tokens, API keys, JWTs, and cookies from prompts or logs before sharing them. - [OpenAPI Security Scheme Linter](/developer-tools/openapi-security-scheme-linter/): Lint OpenAPI security schemes, insecure server URLs, and operations without visible security requirements. - [HAR File Redactor](/developer-tools/har-file-redactor/): Redact cookies, authorization headers, tokens, emails, and secrets from HAR JSON before sending it to support.