<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Disha Gupta</title>
    <description>The latest articles on DEV Community by Disha Gupta (@disha_gupta_91e4b27a012a4).</description>
    <link>https://dev.to/disha_gupta_91e4b27a012a4</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3931814%2F094e661a-f4f9-41cc-99a2-380496f54168.png</url>
      <title>DEV Community: Disha Gupta</title>
      <link>https://dev.to/disha_gupta_91e4b27a012a4</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/disha_gupta_91e4b27a012a4"/>
    <language>en</language>
    <item>
      <title>How I Built an AI-Powered Cloud Security Guardian using AWS — From Idea to Docker in 30 Days</title>
      <dc:creator>Disha Gupta</dc:creator>
      <pubDate>Thu, 14 May 2026 19:03:31 +0000</pubDate>
      <link>https://dev.to/disha_gupta_91e4b27a012a4/how-i-built-an-ai-powered-cloud-security-guardian-using-aws-from-idea-to-docker-in-30-days-7f4</link>
      <guid>https://dev.to/disha_gupta_91e4b27a012a4/how-i-built-an-ai-powered-cloud-security-guardian-using-aws-from-idea-to-docker-in-30-days-7f4</guid>
      <description>&lt;p&gt;How I Built an AI-Powered Cloud Security Guardian — From Idea to Docker in 30 Days&lt;br&gt;
By Disha Gupta |(Cloud Security &amp;amp; GRC) |&lt;a href="https://www.linkedin.com/in/disha-gupta-6588102b9/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/disha-gupta-6588102b9/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Problem That Kept Me Up at Night&lt;/p&gt;

&lt;p&gt;Every week I read another breach report. Misconfigured S3 bucket. IAM user with Administrator Access left active. SSH port 22 open to &lt;code&gt;0.0.0.0/0&lt;/code&gt;. Security groups that nobody audited in six months.&lt;/p&gt;

&lt;p&gt;The frustrating part? These aren't sophisticated zero-days. They're checklist failures. Things that should have been caught automatically.&lt;/p&gt;

&lt;p&gt;So I decided to build the tool I wished existed — one that could scan any AWS account on demand, apply machine learning to risk-score every resource, detect anomalies in CloudTrail logs, and surface remediation advice in real time. I called it "AI Cloud Security Guardian".&lt;/p&gt;

&lt;p&gt;Here's exactly how I built it, what each AWS service does in the architecture, and what I learned the hard way.&lt;/p&gt;

&lt;p&gt;What the Platform Does&lt;/p&gt;

&lt;p&gt;Before getting into the technical stack, here's what Guardian actually does when you use it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You log into the SOC-themed dashboard&lt;/li&gt;
&lt;li&gt;You enter your AWS IAM credentials — they're never stored, only used for that session&lt;/li&gt;
&lt;li&gt;Guardian connects to your AWS account via boto3 and discovers every EC2 instance, S3 bucket, IAM user, IAM role, and security group&lt;/li&gt;
&lt;li&gt;A rule engine runs 9 misconfiguration checks against every resource&lt;/li&gt;
&lt;li&gt;A Random Forest ML model scores each resource from 0.0 to 1.0 based on 6 security features&lt;/li&gt;
&lt;li&gt;An Isolation Forest model runs anomaly detection on CloudTrail logs&lt;/li&gt;
&lt;li&gt;Alerts are generated, prioritized by severity, and surfaced in the dashboard with AI-generated remediation steps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire backend is FastAPI + Python. The frontend is React + TypeScript + Tailwind. Everything runs in Docker. And it connects to real AWS accounts — not mocked data.&lt;/p&gt;

&lt;p&gt;AWS Services Used and Why&lt;/p&gt;

&lt;p&gt;AWS STS — GetCallerIdentity&lt;/p&gt;

&lt;p&gt;The very first AWS API call Guardian makes is &lt;code&gt;sts:GetCallerIdentity&lt;/code&gt;. Before scanning anything, it verifies that your credentials are valid and tells you exactly which account and IAM identity you're using.&lt;/p&gt;

&lt;p&gt;sts = session.client("sts")&lt;br&gt;
identity = sts.get_caller_identity()&lt;/p&gt;

&lt;h1&gt;
  
  
  Returns: Account ID, ARN, UserId
&lt;/h1&gt;

&lt;p&gt;This is the cheapest possible AWS API call — it's always allowed for any valid credential, costs nothing, and gives us a fast-fail before running a 60-second full scan with bad keys. If this call fails, Guardian immediately tells you exactly why — invalid key, expired token, wrong region — instead of failing silently mid-scan.&lt;/p&gt;

&lt;p&gt;What I learned: Always validate credentials with STS before any other AWS operation. It saves enormous debugging time and gives users clear error messages.&lt;/p&gt;

&lt;p&gt;Amazon EC2 — DescribeInstances + DescribeSecurityGroups&lt;/p&gt;

&lt;p&gt;For EC2, Guardian uses two boto3 calls:&lt;/p&gt;

&lt;p&gt;DescribeInstances discovers every running instance and collects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instance ID, type, and state&lt;/li&gt;
&lt;li&gt;Public IP (is it internet-exposed?)&lt;/li&gt;
&lt;li&gt;Associated security group IDs&lt;/li&gt;
&lt;li&gt;IAM instance profile (does it have proper permissions?)&lt;/li&gt;
&lt;li&gt;Key pair name (is access documented?)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DescribeSecurityGroups checks every inbound rule for the most dangerous misconfiguration in AWS — port exposure to &lt;code&gt;0.0.0.0/0&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;def _check_open_to_world(rules):&lt;br&gt;
    for rule in rules:&lt;br&gt;
        for ipv4 in rule.get("IpRanges", []):&lt;br&gt;
            if ipv4.get("CidrIp") == "0.0.0.0/0":&lt;br&gt;
                return True  # CRITICAL finding&lt;br&gt;
    return False&lt;/p&gt;

&lt;p&gt;When Guardian finds SSH (port 22) or RDP (port 3389) open to the entire internet, it fires a Critical alert immediately. This single check has caught the most serious findings in real account scans.&lt;/p&gt;

&lt;p&gt;Detection rules triggered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SG_001&lt;/code&gt; — Open to 0.0.0.0/0 (Critical)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EC2_001&lt;/code&gt; — Public IP with no IAM instance profile (Medium)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EC2_002&lt;/code&gt; — Running instance with no key pair (Low)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Amazon S3 — Multi-API Bucket Analysis&lt;/p&gt;

&lt;p&gt;S3 is where most data breaches start. Guardian runs five separate API calls per bucket:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API Call&lt;/th&gt;
&lt;th&gt;What We Check&lt;/th&gt;
&lt;th&gt;Severity if Missing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_public_access_block&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All 4 Block Public Access flags&lt;/td&gt;
&lt;td&gt;Critical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_bucket_encryption&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SSE-S3 or SSE-KMS enabled&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_bucket_logging&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Access logs configured&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_bucket_versioning&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Object versioning enabled&lt;/td&gt;
&lt;td&gt;Info&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_bucket_location&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Region (for context)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The public access check is the most important. AWS has four separate flags for blocking public access (&lt;code&gt;BlockPublicAcls&lt;/code&gt;, &lt;code&gt;IgnorePublicAcls&lt;/code&gt;, &lt;code&gt;BlockPublicPolicy&lt;/code&gt;, &lt;code&gt;RestrictPublicBuckets&lt;/code&gt;). Guardian checks that ALL four are enabled — if even one is &lt;code&gt;False&lt;/code&gt;, the bucket is marked as potentially public:&lt;/p&gt;

&lt;p&gt;fully_blocked = all([&lt;br&gt;
    cfg.get("BlockPublicAcls", False),&lt;br&gt;
    cfg.get("IgnorePublicAcls", False),&lt;br&gt;
    cfg.get("BlockPublicPolicy", False),&lt;br&gt;
    cfg.get("RestrictPublicBuckets", False),&lt;br&gt;
])&lt;br&gt;
data["is_public"] = not fully_blocked&lt;/p&gt;

&lt;p&gt;What I learned: The absence of a public access block configuration is different from having it set to False. If &lt;code&gt;get_public_access_block&lt;/code&gt; throws a &lt;code&gt;NoSuchPublicAccessBlockConfiguration&lt;/code&gt; exception, the bucket has no protection at all — Guardian treats that as &lt;code&gt;is_public = True&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;AWS IAM — Privilege and MFA Analysis&lt;/p&gt;

&lt;p&gt;IAM scanning is where Guardian finds the highest-severity issues. Three API calls cover the critical checks:&lt;/p&gt;

&lt;p&gt;ListAttachedUserPolicies — checks every user for &lt;code&gt;AdministratorAccess&lt;/code&gt;. One IAM user with full admin and no MFA is game over if their credentials leak.&lt;/p&gt;

&lt;p&gt;ListMFADevices — for any user with console access, Guardian checks if MFA is enabled. No MFA on a console user is an automatic High finding.&lt;/p&gt;

&lt;p&gt;GetLoginProfile — determines if a user has console access at all. Service accounts should never have console passwords.&lt;/p&gt;

&lt;h1&gt;
  
  
  The most dangerous combination in AWS:
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Console access + AdministratorAccess + no MFA
&lt;/h1&gt;

&lt;p&gt;if user.has_console_access and user.is_admin and not user.has_mfa:&lt;br&gt;
    # This is a three-alarm fire&lt;br&gt;
    generate_critical_alert(user)&lt;/p&gt;

&lt;p&gt;The IAM scan also covers roles — any role with &lt;code&gt;AdministratorAccess&lt;/code&gt; attached gets flagged as High, because a compromised EC2 instance or Lambda function with that role has unlimited blast radius.&lt;/p&gt;

&lt;p&gt;Detection rules triggered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;IAM_001&lt;/code&gt; — User with AdministratorAccess (Critical)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IAM_002&lt;/code&gt; — Console user without MFA (High)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IAM_003&lt;/code&gt; — Role with AdministratorAccess (High)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS CloudTrail — Log Analysis for Anomaly Detection&lt;/p&gt;

&lt;p&gt;This is where the ML comes in. CloudTrail logs every API call made in your AWS account. Guardian ingests these events and runs them through an Isolation Forest — an unsupervised ML algorithm that identifies statistical outliers without needing labeled training data.&lt;/p&gt;

&lt;p&gt;The feature extraction aggregates per-user behavior across a time window:&lt;/p&gt;

&lt;p&gt;features = {&lt;br&gt;
    "api_call_count":    1,      # volume of API calls&lt;br&gt;
    "failed_logins":     1,      # AccessDenied errors&lt;br&gt;
    "hour_of_day":       3,      # off-hours = suspicious&lt;br&gt;
    "is_new_region":     True,   # never seen this region before&lt;br&gt;
    "bytes_transferred": 0,&lt;br&gt;
    "unique_resources":  1,&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Isolation Forest works by randomly partitioning the feature space. Anomalous data points — like a user suddenly making 5,000 API calls from a new region at 3 AM with 50 Access Denied errors — are isolated quickly because they're far from the normal distribution. The algorithm assigns an anomaly score where lower = more anomalous.&lt;/p&gt;

&lt;p&gt;What Guardian flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unusual API call volume (potential credential theft / cryptomining)&lt;/li&gt;
&lt;li&gt;Access from a previously unseen AWS region (potential account takeover)&lt;/li&gt;
&lt;li&gt;Off-hours access patterns (lateral movement)&lt;/li&gt;
&lt;li&gt;Repeated AccessDenied errors (brute force / privilege escalation attempt)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why Isolation Forest over supervised ML? Because you almost never have labeled "malicious" CloudTrail logs to train on. Isolation Forest requires no labels — it just learns what "normal" looks like and flags deviations. This is exactly how real SIEM tools work.&lt;/p&gt;

&lt;p&gt;The ML Risk Scoring Engine&lt;/p&gt;

&lt;p&gt;Beyond rule-based detection, every resource gets a continuous risk score from 0.0 to 1.0 using a "Random Forest classifier".&lt;/p&gt;

&lt;p&gt;The model uses 6 features derived from each resource:&lt;/p&gt;

&lt;p&gt;FEATURES = [&lt;br&gt;
    "public_access",        # 0/1 — internet-exposed?&lt;br&gt;
    "open_ports",           # count of world-open inbound rules&lt;br&gt;
    "encryption_enabled",   # 0/1 — data encrypted at rest?&lt;br&gt;
    "iam_privilege_level",  # 0=none, 1=read, 2=write, 3=admin&lt;br&gt;
    "mfa_enabled",          # 0/1 — MFA enforced?&lt;br&gt;
    "logging_enabled",      # 0/1 — audit trail active?&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;The model is trained on synthetic data at startup and saved to disk with &lt;code&gt;joblib&lt;/code&gt;. In a production deployment with real historical findings, you'd replace the synthetic training data with actual labeled security findings from past scans — making the model progressively more accurate with each scan.&lt;/p&gt;

&lt;p&gt;Risk levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Critical — score ≥ 0.75&lt;/li&gt;
&lt;li&gt;High — score ≥ 0.55&lt;/li&gt;
&lt;li&gt;Medium — score ≥ 0.35&lt;/li&gt;
&lt;li&gt;Low — score ≥ 0.15&lt;/li&gt;
&lt;li&gt;Minimal — score &amp;lt; 0.15&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security Architecture Decisions&lt;/p&gt;

&lt;p&gt;Building a tool that handles AWS credentials forced me to think carefully about security at every layer.&lt;/p&gt;

&lt;p&gt;Credentials Never Touch Storage&lt;/p&gt;

&lt;p&gt;The most important design decision: AWS credentials are never stored anywhere. Not in the database. Not in logs. Not in the browser's localStorage. They exist only in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user's browser state (React &lt;code&gt;useState&lt;/code&gt;) for the duration of the modal&lt;/li&gt;
&lt;li&gt;The HTTP request body while in transit&lt;/li&gt;
&lt;li&gt;Python function parameters during the scan&lt;/li&gt;
&lt;li&gt;Cleared immediately after the scan completes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After scan completes — credentials go out of scope and are GC'd&lt;br&gt;
def run_full_scan_with_credentials(access_key_id, secret_access_key, ...):&lt;br&gt;
     ... scan happens ...&lt;br&gt;
    return result&lt;br&gt;
     access_key_id and secret_access_key are never written anywhere&lt;/p&gt;

&lt;p&gt;The backend logs only the key prefix (&lt;code&gt;AKIA...8chars...&lt;/code&gt;) for debugging — never the full key or secret.&lt;/p&gt;

&lt;p&gt;JWT in Memory, Not localStorage&lt;/p&gt;

&lt;p&gt;The dashboard JWT token lives in a module-level JavaScript variable — not &lt;code&gt;localStorage&lt;/code&gt; or &lt;code&gt;sessionStorage&lt;/code&gt;. This prevents XSS attacks from stealing the token, at the cost of losing the session on page refresh (acceptable for a security tool).&lt;/p&gt;

&lt;p&gt;// In-memory only — XSS cannot read this via document.cookie or localStorage&lt;br&gt;
let _accessToken: string | null = null&lt;/p&gt;

&lt;p&gt;An auto-logout timer is set from the JWT's &lt;code&gt;exp&lt;/code&gt; claim with a 30-second buffer. When the token is about to expire, the user is automatically logged out.&lt;/p&gt;

&lt;p&gt;Input Validation at Every Layer&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend: Regex validation on the Access Key ID format, length checks on all fields&lt;/li&gt;
&lt;li&gt;Backend: Pydantic &lt;code&gt;field_validator&lt;/code&gt; on every credential field before any AWS call&lt;/li&gt;
&lt;li&gt;JSON parsing: &lt;code&gt;safeJsonParse()&lt;/code&gt; blocks &lt;code&gt;__proto__&lt;/code&gt; and &lt;code&gt;constructor&lt;/code&gt; keys to prevent prototype pollution in user-submitted log data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Tech Stack&lt;/p&gt;

&lt;p&gt;Backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FastAPI — async Python web framework, auto-generates OpenAPI docs&lt;/li&gt;
&lt;li&gt;SQLAlchemy + SQLite (dev) / PostgreSQL (prod) — ORM for findings storage&lt;/li&gt;
&lt;li&gt;boto3 — AWS SDK, all credential operations&lt;/li&gt;
&lt;li&gt;scikit-learn — Random Forest (risk scoring) + Isolation Forest (anomaly detection)&lt;/li&gt;
&lt;li&gt;Pydantic v2 — request validation and settings management&lt;/li&gt;
&lt;li&gt;JWT via &lt;code&gt;python-jose&lt;/code&gt; — stateless authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Frontend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React 18 + TypeScript — component framework&lt;/li&gt;
&lt;li&gt;Tailwind CSS — utility-first styling with custom SOC terminal design tokens&lt;/li&gt;
&lt;li&gt;React Query (TanStack) — server state management with caching&lt;/li&gt;
&lt;li&gt;Recharts — risk score visualization (bar charts, donut charts, radar charts)&lt;/li&gt;
&lt;li&gt;Axios — HTTP client with request/response interceptors&lt;/li&gt;
&lt;li&gt;DOMPurify — XSS sanitization for any server-returned strings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DevOps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker — multi-stage builds (builder → slim runtime) for both services&lt;/li&gt;
&lt;li&gt;Docker Compose — orchestrates PostgreSQL + FastAPI + Nginx as one stack&lt;/li&gt;
&lt;li&gt;Kubernetes — 7 manifests covering namespace, secrets, deployments, ingress, and HPA autoscaling&lt;/li&gt;
&lt;li&gt;Nginx — reverse proxy in the frontend container, eliminates CORS entirely in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Challenges and What I Actually Learned&lt;/p&gt;

&lt;p&gt;Challenge 1: The Variable Name Collision That Caused Every Scan to 500&lt;/p&gt;

&lt;p&gt;For days, every scan attempt returned a 500 Internal Server Error. The backend logs showed a &lt;code&gt;TypeError: 'bool' is not callable&lt;/code&gt;. After hours of debugging, I found it:&lt;/p&gt;

&lt;p&gt;BROKEN — parameter named scan_security_groups shadows the function&lt;br&gt;
def run_full_scan(scan_security_groups: bool = True):&lt;br&gt;
    ...&lt;br&gt;
    "security_groups": scan_security_groups(session)  # calling a bool!&lt;/p&gt;

&lt;p&gt;The function &lt;code&gt;scan_security_groups()&lt;/code&gt; and the boolean parameter &lt;code&gt;scan_security_groups&lt;/code&gt; had the same name. Python used the parameter, not the function. Fixed by prefixing all internal scanner functions with &lt;code&gt;_do_&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;"security_groups": _do_scan_security_groups(session) if scan_security_groups else []&lt;/p&gt;

&lt;p&gt;Lesson: In Python, function parameters shadow module-level names within their scope. Name your parameters explicitly to avoid conflicts with functions they might call.&lt;/p&gt;

&lt;p&gt;Challenge 2: CORS That Wasn't Actually CORS&lt;/p&gt;

&lt;p&gt;The frontend was getting blocked by CORS policy — but the backend had &lt;code&gt;allow_origins=["*"]&lt;/code&gt; set. After wasting an afternoon, I realized the issue: FastAPI's &lt;code&gt;CORSMiddleware&lt;/code&gt; with &lt;code&gt;allow_origins=["*"]&lt;/code&gt; is &lt;strong&gt;incompatible&lt;/strong&gt; with &lt;code&gt;allow_credentials=True&lt;/code&gt;. Setting both is illegal per the CORS spec and FastAPI silently breaks the middleware.&lt;/p&gt;

&lt;p&gt;The final fix wasn't even CORS middleware — it was switching to a Vite proxy in development. The browser calls &lt;code&gt;localhost:5173/api/scan/aws&lt;/code&gt;, Vite forwards it to &lt;code&gt;localhost:8000/scan/aws&lt;/code&gt; server-side. The browser never makes a cross-origin request. CORS doesn't apply.&lt;/p&gt;

&lt;p&gt;Lesson: The right fix for CORS in development is a proxy, not CORS headers. Save CORS configuration for production where you actually need it.&lt;/p&gt;

&lt;p&gt;Challenge 3: TypeScript Strict Mode vs Docker Build&lt;/p&gt;

&lt;p&gt;The code compiled fine locally with VS Code's TypeScript server being lenient. But the Docker build ran &lt;code&gt;tsc&lt;/code&gt; in strict mode and found 15 errors — unused parameters, &lt;code&gt;import.meta.env&lt;/code&gt; type issues, missing module declarations, type assertion errors.&lt;/p&gt;

&lt;p&gt;The fix was a combination of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting &lt;code&gt;"strict": false&lt;/code&gt; and &lt;code&gt;"noUnusedLocals": false&lt;/code&gt; in &lt;code&gt;tsconfig.json&lt;/code&gt; for the build&lt;/li&gt;
&lt;li&gt;Accessing &lt;code&gt;import.meta.env&lt;/code&gt; via &lt;code&gt;(import.meta as any).env&lt;/code&gt; to bypass the strict type check&lt;/li&gt;
&lt;li&gt;Removing &lt;code&gt;"references"&lt;/code&gt; from &lt;code&gt;tsconfig.json&lt;/code&gt; so the build didn't look for &lt;code&gt;tsconfig.node.json&lt;/code&gt; inside the Docker container&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Lesson: Always test your Docker build on CI before you think you're done. Local TypeScript compilation and Docker-in-builder-stage compilation can behave very differently.&lt;/p&gt;

&lt;p&gt;The Minimum IAM Policy&lt;/p&gt;

&lt;p&gt;For anyone who wants to scan their own account, here's the exact minimum permission set needed:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "Version": "2012-10-17",&lt;br&gt;
  "Statement": [&lt;br&gt;
    {&lt;br&gt;
      "Effect": "Allow",&lt;br&gt;
      "Action": [&lt;br&gt;
        "sts:GetCallerIdentity",&lt;br&gt;
        "ec2:DescribeInstances",&lt;br&gt;
        "ec2:DescribeSecurityGroups",&lt;br&gt;
        "s3:ListAllMyBuckets",&lt;br&gt;
        "s3:GetBucketPublicAccessBlock",&lt;br&gt;
        "s3:GetBucketEncryption",&lt;br&gt;
        "s3:GetBucketLogging",&lt;br&gt;
        "iam:ListUsers",&lt;br&gt;
        "iam:ListRoles",&lt;br&gt;
        "iam:ListMFADevices",&lt;br&gt;
        "iam:ListAttachedUserPolicies",&lt;br&gt;
        "iam:GetLoginProfile",&lt;br&gt;
        "cloudtrail:LookupEvents"&lt;br&gt;
      ],&lt;br&gt;
      "Resource": "*"&lt;br&gt;
    }&lt;br&gt;
  ]&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Create a dedicated IAM user with only this policy. Never use root credentials or your personal admin account.&lt;/p&gt;

&lt;p&gt;The full project — backend, frontend, Docker, and Kubernetes manifests — is on GitHub -&lt;a href="https://github.com/Dianger16/AWS-CLOUD-SOC.git" rel="noopener noreferrer"&gt;https://github.com/Dianger16/AWS-CLOUD-SOC.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stack summary: FastAPI · boto3 · scikit-learn · React · TypeScript · Tailwind · Docker · Kubernetes&lt;/p&gt;

&lt;p&gt;If you're working on cloud security, GRC, or DevSecOps and want to collaborate or discuss the architecture, I'm always open to connect.-"&lt;a href="https://www.linkedin.com/in/disha-gupta-6588102b9/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/disha-gupta-6588102b9/&lt;/a&gt;"&lt;/p&gt;

</description>
      <category>aws</category>
      <category>awscommunitybuilders</category>
      <category>cloud</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
