DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Security

The topic of security covers many different facets within the SDLC. From focusing on secure application design to designing systems to protect computers, data, and networks against potential attacks, it is clear that security should be top of mind for all developers. This Zone provides the latest information on application vulnerabilities, how to incorporate security earlier in your SDLC practices, data governance, and more.

icon
Latest Premium Content
Trend Report
Security by Design
Security by Design
Refcard #388
Threat Modeling Core Practices
Threat Modeling Core Practices
Refcard #402
SBOM Essentials
SBOM Essentials

DZone's Featured Security Resources

Why Continuous Application Security Testing Is No Longer Optional

Why Continuous Application Security Testing Is No Longer Optional

By Jigar Shah
Your last pentest is already out of date. The moment you shipped new code after that report, your risk profile changed, and nobody re-tested it. That's the reality most teams are living in. Nearly 29% of organizations still lack continuous vulnerability monitoring, relying instead on periodic scans that miss threats attackers are actively exploiting right now. Annual testing made sense when releases happened twice a year. It doesn't anymore. Your CI/CD pipeline ships changes weekly, your APIs multiply monthly, and your attack surface never stops moving. This piece breaks down why continuous testing has shifted from a security team's wish list to a business requirement, and what it actually takes to build one that works. Understanding Continuous Application Security Testing Continuous application security testing is the practice of running automated security checks every time your code changes. Instead of waiting for a scheduled quarterly review, you should scan your pipelines during daily deployments. This approach ensures that no code goes live without a fast security check. According to the Black Duck BSIMM16 report, high-performing engineering teams integrate real-time vulnerability detection directly into their CI/CD pipelines. This process relies on an automated security testing approach to discover software flaws instantly. It shifts security from a periodic event to a constant, background process. The main focus of this approach is continuous testing with exploit validation. It catches critical flaws like broken object-level authorization before attackers can exploit them. By checking your attack surface daily, you protect live applications without forcing your development team to slow down. Why Traditional Security Testing Is No Longer Enough Traditional security testing fails because modern software development moves too fast. Legacy assessment methods like quarterly scans cannot keep up with rapid deployment cycles, leaving application endpoints exposed to real-world threats. Rapid Deployment Cycles Break Periodic Schedules Modern development teams ship code changes daily or hourly. A scheduled security test only captures a single moment in time. The very next code push can introduce critical flaws, making a recent assessment report completely obsolete. The Exploit Window is Shrinking Rapidly Attackers utilize advanced solutions to scan vulnerabilities immediately after discovery. According to recent Cybersecurity and Infrastructure Security Agency (CISA) reports, threat actors target new flaws within hours. Waiting months for a security scan leaves a massive window open. Modern App Architectures Increase Attack Surfaces Applications rely heavily on cloud APIs, microservices, and micro-frontend structures. This creates complex data paths that static legacy testing cannot map. Without continuous verification, broken access controls and hidden data leaks go completely unnoticed inside these sprawling networks. Compliance Audits Fail to Prevent Attacks Passing a standard compliance audit does not guarantee active protection. Regulatory checks often focus on documentation and basic patch levels rather than real-world exploit validation. A system can achieve compliance while remaining completely vulnerable to active web exploits. High False Positive Rates Drain Engineering Resources Legacy security testing methods often produce massive lists of unverified bugs. Security teams waste hours manually filtering out false alarms. This friction slows down software delivery and causes friction between development groups and security personnel. The Biggest Risks of Not Testing Continuously Skipping real-time security reviews exposes production code to critical software vulnerabilities. Without continuous validation, hidden entry points and data security gaps remain open for attackers to exploit. Accumulating severe security debt: Untested code builds up flaws over time. This makes future remediation complex and highly expensive for engineering teams to resolve. Exploited broken object-level authorization: Attackers target unverified API endpoints easily. They manipulate object identifiers to gain unauthorized access to sensitive user data. Silent third-party dependency exploits: Open-source libraries introduce hidden bugs regularly. Without real-time dependency scanning, malicious updates can compromise your entire software supply chain unnoticed. Unchecked web application misconfigurations: S3 buckets and access control rules get altered during fast updates. These small changes expose critical databases to the public web. Extended attacker dwell time: Threat actors slip into quiet system gaps easily. They steal data for months before periodic testing cycles finally flag the breach. Costly emergency patch deployments: Discovering severe flaws right before an audit forces rushed fixes. This disrupts product roadmaps and introduces unstable code into production systems. Compliance failure and financial penalties: Lacking continuous monitoring violates modern data privacy mandates. This leads to failed security audits and heavy regulatory fines for your business. Business Value of Moving to a Continuous Security Model Transitioning to continuous security safeguards critical digital assets while optimizing engineering speed. Real-time exploit validation protects corporate reputation, ensures regulatory compliance, and reduces the financial impact of data breaches. Lower Remediation and Engineering Costs Fixing software vulnerabilities early in the development lifecycle is significantly cheaper. Continuous validation prevents security flaws from reaching production. This eliminates the need for expensive emergency hotfixes and saves valuable developer hours. Faster Secure Software Delivery Integrating security directly into CI/CD pipelines eliminates late-stage deployment bottlenecks. Engineering teams ship functional updates with confidence, knowing automated scans run in the background. Security becomes an accelerator rather than a roadblock. Frictionless Compliance and Audit Readiness Continuous monitoring maintains a constant state of compliance with frameworks like GDPR and PCI DSS. Instead of scrambling before annual security audits, organizations retain historical evidence of active threat management. Reduced True Positive Alert Fatigue Advanced testing platforms prioritize real exploit validation over theoretical bug lists. Filtering out false positives ensures security operations center teams only focus on validated threats. This focus optimizes overall incident response efficiency. Stronger Customer Trust and Brand Equity Demonstrating proactive data protection builds deep trust with enterprise clients. Continuous application testing proves your organization prioritizes information security. This competitive advantage helps accelerate sales cycles and protects brand reputation. Best Practices for Implementing Continuous Application Security Testing Deploying continuous security requires blending automation seamlessly into existing developer workflows. Following industry blueprints ensures real-time exploit validation keeps application platforms secure without disrupting rapid software release cycles. Integrate scans directly into CI/CD pipelines: Embed automated security into your daily deployment pipeline. Running fast vulnerability checks on every code commit stops bugs before they reach production. Focus on live exploit validation: Prioritize an approach that actively tests whether a bug is truly exploitable or not. Confirming real attack paths eliminates time wasted on harmless false positives. Automate API endpoint discovery: Modern web apps shift constantly. Use a dynamic discovery approach to find hidden endpoints and protect against broken object-level authorization gaps automatically. Implement real-time dependency tracking: Scan open-source packages during every build cycle. This process flags vulnerable third-party libraries and protects your software supply chain instantly. Combine automation with strategic manual tests: Automated scanning handles repetitive code checking efficiently. Use manual penetration testing for complex business logic flaws that automated scanners miss. Enable MFA-aware testing flows: Ensure your testing software can bypass multifactor authentication securely. Authentic user journey testing reveals hidden security flaws inside deep, protected application layers. Train developers on remediation context: Provide engineering teams with clear exploit evidence right inside their dashboards. Detailed contextual reports help developers fix critical flaws quickly without extra friction. Wrapping Up Point-in-time testing was never designed for how software ships today. Weekly deployments, expanding APIs, and AI-generated code all move faster than a once-a-year security check can track. Continuous application security testing closes that gap. It catches vulnerabilities the moment they enter your codebase, validates real exploitability, and gives your team evidence, not guesswork, when auditors come asking. The organizations pulling ahead aren't the ones testing more often. They're the ones testing continuously, prioritizing exploitable risk over noise, and treating security as part of how they build, not an afterthought. More
Part 2: Securing and Scaling Goose-to-Java Agent Traffic With agentgateway

Part 2: Securing and Scaling Goose-to-Java Agent Traffic With agentgateway

By Daniel Oh DZone Core CORE
In Part 1 of this series, we built a Quarkus-based MCP tool server and connected it to the Goose AI agent over Streamable HTTP. The tools worked, the demo was clean, and everything ran on localhost. But the moment you imagine 50 developers running Goose on their laptops, all hitting the same set of backend MCP servers, the architecture starts to crack. Who authenticated that tool call? Which role authorized the getAuditTrail invocation? What stops a poisoned tool name from injecting payloads into your backend? This article answers those questions by placing agentgateway — the Linux Foundation's open-source proxy for agentic AI traffic — between Goose clients and the Quarkus MCP microservices we built in Part 1. The Problem: Direct Agent-to-Backend Connections Don't Scale When Goose (or any MCP client) connects directly to a backend MCP server, every tool call is a point-to-point trust relationship: This works for demos. It breaks in production for three reasons: No authentication. The MCP Streamable HTTP endpoint accepts any JSON-RPC call. There is no token verification, no session binding, and no identity propagation.No authorization. Every caller can invoke every tool. An intern running Goose has the same access as an SRE — getAuditTrail, getOrderStatus, everything.No guardrails. A compromised or misconfigured agent can send tool names containing prototype-pollution payloads (__proto__), path-traversal sequences (../), or CRLF-injected headers. The backend has to defend itself alone. The Solution: agentgateway as a Unified Control Plane agentgateway is a Rust-based proxy purpose-built for AI agent traffic. It understands the MCP protocol natively — it doesn't just forward HTTP; it parses JSON-RPC envelopes, manages MCP sessions, and applies policies at the tool-call level. Here is the architecture we're building: Goose connects to agentgateway on port 3000. agentgateway validates the JWT, checks the caller's roles against tool-level RBAC rules, passes the call through an ExtMCP guardrail server that sanitizes headers and blocks poisoning attempts, and only then forwards the clean request to the Quarkus backend on port 8080. Prerequisites You'll need everything from Part 1, plus: agentgateway binary (v1.4+): Shell curl -sL https://agentgateway.dev/install | bash Verify your Part 1 Quarkus MCP server still works: Shell cd part1-quarkus-mcp mvn quarkus:dev Then confirm the MCP endpoint responds: Shell curl -s http://localhost:8080/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}' | jq . Step 1: Deploy agentgateway Alongside the Quarkus MCP Server Create the agentgateway configuration at part2-agentgateway/agentgateway/config-dev.yaml. This development config proxies MCP traffic without requiring JWT, so you can validate the plumbing first: YAML # yaml-language-server: $schema=https://agentgateway.dev/schema/config mcp: port: 3000 policies: cors: allowOrigins: - "*" allowHeaders: - mcp-protocol-version - content-type - mcp-session-id exposeHeaders: - Mcp-Session-Id targets: - name: customer-tools mcp: host: http://localhost:8080/mcp Start agentgateway: YAML agentgateway -f part2-agentgateway/agentgateway/config-dev.yaml Now test the proxied MCP endpoint. Note that agentgateway returns SSE format (event: message\ndata: {...}), so we extract the JSON from the data: line: Shell curl -s http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-03-26" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}' \ | grep '^data: ' | sed 's/^data: //' | jq . You should see the same customer-tools server info as Part 1, but the traffic now flows through agentgateway. Open http://localhost:15000/ui to see the agentgateway admin UI with your MCP target listed. Step 2: Add JWT Authentication With the proxy working, let's lock it down. The mcpAuthentication policy implements the MCP Authorization specification — it validates JWT bearer tokens on every MCP request and supports OAuth 2.1 with PKCE for browser-based flows. Update the config to part2-agentgateway/agentgateway/config.yaml: YAML # yaml-language-server: $schema=https://agentgateway.dev/schema/config mcp: port: 3000 policies: cors: allowOrigins: - "*" allowHeaders: - mcp-protocol-version - content-type - mcp-session-id - authorization exposeHeaders: - Mcp-Session-Id mcpAuthentication: issuer: http://localhost:9000 audiences: - "http://localhost:3000/mcp" jwks: url: http://localhost:9000/.well-known/jwks.json resourceMetadata: resource: http://localhost:3000/mcp scopesSupported: - "mcp:tools:read" - "mcp:tools:execute" bearerMethodsSupported: - header targets: - name: customer-tools mcp: host: http://localhost:8080/mcp How It Works When a Goose client (or any MCP client) connects to http://localhost:3000/mcp: Discovery. The client fetches /.well-known/oauth-protected-resource from agentgateway and discovers it needs a bearer token with the mcp:tools:execute scope.Token acquisition. The client runs the OAuth 2.1 Authorization Code flow with PKCE against the issuer (http://localhost:9000), obtains an access token, and includes it as Authorization: Bearer <token> on subsequent MCP requests.Validation. agentgateway downloads the JWKS from the issuer, verifies the token signature, checks exp, iss, and aud claims, and extracts the sub and role claims for downstream authorization.Forwarding. Only after validation does agentgateway forward the JSON-RPC call to the Quarkus backend. Connecting to a Real OIDC Provider For production, replace the issuer and JWKS URL with your OIDC provider. Here is an example using Keycloak: YAML mcpAuthentication: issuer: https://keycloak.example.com/realms/mcp audiences: - "https://gateway.example.com/mcp" jwks: url: https://keycloak.example.com/realms/mcp/protocol/openid-connect/certs provider: keycloak: {} agentgateway has built-in support for Keycloak, Auth0, Okta, Microsoft Entra ID, and other OIDC providers. Step 3: Configure Tool-Level RBAC With CEL Expressions JWT authentication tells you who is calling. MCP authorization tells you what they're allowed to do. agentgateway uses CEL (Common Expression Language) to define fine-grained, tool-level RBAC rules. Add the mcpAuthorization policy to your config: YAML mcpAuthorization: rules: # Operators can call any tool - 'has(jwt.roles) && "operator" in jwt.roles' # Viewers can only read status and health - > has(jwt.roles) && "viewer" in jwt.roles && mcp.tool.name in ["getCustomerStatus", "getZoneHealthLogs", "getSLACompliance"] # Auditors can access audit trail and SLA compliance - > has(jwt.roles) && "auditor" in jwt.roles && mcp.tool.name in ["getAuditTrail", "getSLACompliance"] How the Rules Work Each rule is a CEL expression that evaluates to true (allow) or false (deny). agentgateway evaluates them in order — the first match wins. RoleAllowed ToolsDenied ToolsoperatorAll five toolsNoneviewergetCustomerStatus, getZoneHealthLogs, getSLACompliancegetOrderStatus, getAuditTrailauditorgetAuditTrail, getSLACompliancegetCustomerStatus, getZoneHealthLogs, getOrderStatusNo roleNoneAll These aren't abstract labels — at Acme FinServ they map to real people and a real segregation-of-duties story: PersonaRoleWhy this scopeSofia — SRE, on-call for the platformoperatorNeeds to drive operational tools during incidents; full access is justified and logged.Acme Status Dashboard — an internal read-only serviceviewerShows customers and health at a glance; must never read getOrderStatus or getAuditTrail (PII/financial).Priya — external SOC 2 auditorauditorReviews the audit trail and SLA posture only. Giving her getCustomerStatus would violate least privilege — an auditor reading live customer data is itself a finding. The auditor scope is the one a SOC 2 assessor will scrutinize: it proves the audit function is separated from the operational function, and that access is granted by need, not convenience. agentgateway also auto-filters tools/list responses — if a viewer calls tools/list, they only see the three tools they're authorized to invoke. The agent never even learns that getAuditTrail exists. Available CEL Variables VariableDescriptionmcp.tool.nameThe tool being invoked (e.g., getCustomerStatus)mcp.tool.targetThe backend target name (e.g., customer-tools)jwt.subThe subject claim from the JWTjwt.rolesRole claims extracted from the JWThas(jwt.<claim>)Check whether a JWT claim exists Step 4: Prevent Tool Poisoning With ExtMCP Guardrails JWT and RBAC protect the identity layer. Guardrails protect the content layer. A valid, authenticated operator can still send a tool call with a poisoned name like getCustomerStatus/../../../etc/passwd or arguments containing <script> tags. The Quarkus backend's @Pattern annotations from Part 1 catch some of this, but defense in depth means filtering at the proxy too. agentgateway's ExtMCP guardrails intercept MCP method calls before they reach the backend, passing them through an external gRPC policy server that can inspect, mutate, or deny each call. Building the Guardrail Server With Quarkus gRPC Instead of relying on a third-party Docker image, we'll build our own ExtMCP guardrail server using Quarkus gRPC — keeping the entire stack in Java. The guardrail server lives in part2-agentgateway/extmcp-guardrail/ and implements the agentgateway ExtMCP protocol. First, the protobuf service definition (src/main/proto/extmcp.proto): ProtoBuf syntax = "proto3"; package agentgateway.dev.ext_mcp; option java_package = "com.example.guardrail.grpc"; import "google/protobuf/struct.proto"; service ExtMcp { rpc CheckRequest (McpRequest) returns (McpRequestResult); rpc CheckResponse (McpResponse) returns (McpResponseResult); } message McpRequest { repeated string service_names = 1; string method = 2; google.protobuf.Struct metadata_context = 3; optional bytes mcp_request = 4; repeated McpHeader headers = 5; } message McpRequestResult { oneof result { Pass pass = 1; bytes mutated = 2; AuthorizationError error = 3; } HeaderMutation header_mutation = 4; } message AuthorizationError { enum Code { UNKNOWN = 0; PERMISSION_DENIED = 1; RESOURCE_EXHAUSTED = 2; INVALID = 3; } Code code = 1; string reason = 2; optional bytes mcp_error = 3; } The Quarkus service implementation performs header sanitization and tool-poisoning detection: Java @GrpcService public class ExtMcpGuardrailService implements ExtMcp { private static final Pattern DANGEROUS_HEADER = Pattern.compile( "(?i)^(x-mcp-|x-forwarded-|x-real-ip)"); private static final List<String> BLOCKED_PATTERNS = List.of( "__proto__", "constructor", "../", "eval(", "exec(", "<script"); @Override public Uni<McpRequestResult> checkRequest(McpRequest request) { if (!"tools/call".equals(request.getMethod())) { return passRequest(); } // 1. Sanitize x-mcp-* headers for CRLF injection String headerError = sanitizeHeaders(request.getHeadersList()); if (headerError != null) { return denyRequest("header sanitization failed: " + headerError); } // 2. Check tool name and arguments for poisoning patterns if (request.hasMcpRequest()) { String poisonError = checkToolPoisoning( request.getMcpRequest().toStringUtf8()); if (poisonError != null) { return denyRequest("tool poisoning detected: " + poisonError); } } return passRequest(); } @Override public Uni<McpResponseResult> checkResponse(McpResponse response) { if (!"tools/list".equals(response.getMethod())) { return passResponse(); } // Append [guardrail-verified] marker to every tool description String original = response.getMcpResponse().toStringUtf8(); String mutated = original.replace("\"description\":\"", "\"description\":\"[guardrail-verified] "); return Uni.createFrom().item(McpResponseResult.newBuilder() .setMutated(ByteString.copyFrom(mutated, StandardCharsets.UTF_8)) .build()); } } Start the guardrail server on port 9001: Shell cd part2-agentgateway/extmcp-guardrail mvn quarkus:dev Configuring the Guardrail Policy Add the mcpGuardrails section to the agentgateway config: YAML mcpGuardrails: processors: - kind: remote host: "localhost:9001" failureMode: failClosed methods: tools/call: request tools/list: response The key settings: SettingValueWhyfailureModefailClosedIf the guardrail server is down, deny all tool calls rather than allowing unfiltered traffictools/call: requestPre-forwardInspect and sanitize before the call reaches the Quarkus backendtools/list: responsePost-forwardAnnotate or filter the tool list after the backend responds How Tool Poisoning Prevention Works When a tools/call request arrives, the guardrail flow is: Plain Text Goose → agentgateway → [JWT verified] → [RBAC checked] → → ExtMCP CheckRequest() → guardrail server inspects: 1. Scan x-mcp-* headers for CRLF injection 2. Validate header value lengths (≤ 256 bytes) 3. Check tool name for blocked patterns (__proto__, ../, eval()...) 4. Check tool arguments for injection payloads → Pass / Mutate / Deny → [if passed] → Quarkus MCP backend Sanitizing x-mcp-header Values The x-mcp-* headers carry protocol metadata between MCP clients and servers. A malicious client can inject CRLF sequences (\r\n) into these headers to smuggle additional HTTP headers or split responses. The guardrail server strips these by: Matching any header whose name starts with x-mcp-, x-forwarded-, or x-real-ipRejecting values that contain \r or \n charactersEnforcing a 256-byte maximum length on these header values Building a Custom Guardrail Server For production, implement the ExtMCP gRPC protocol with two methods: CheckRequest – Called before the tool call reaches the backend. Inspect the tool name, arguments, and headers. Return Pass, Mutate (rewrite params), or Deny with an AuthorizationError.CheckResponse – Called after the backend responds. Inspect the result. Return Pass, Mutate (redact sensitive data), or Deny. The part2-agentgateway/extmcp-guardrail/ directory contains the complete Quarkus gRPC implementation with the proto definition, the guardrail service, and the Maven build. Verifying the Guardrail With all three services running, test that the guardrail is active: Shell # Initialize session export MCP_SESSION_ID=$(curl -s -D - http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-03-26" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}' \ | grep -i "mcp-session-id:" | sed 's/.*: //' | tr -d '\r') # Complete handshake curl -s http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-03-26" \ -H "mcp-session-id: $MCP_SESSION_ID" \ -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' # List tools — descriptions should show the guardrail marker curl -s http://localhost:3000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-03-26" \ -H "mcp-session-id: $MCP_SESSION_ID" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}' \ | grep '^data: ' | sed 's/^data: //' | jq '.result.tools[].description' Each tool description should start with the [guardrail-verified] marker, confirming that every tools/list response passes through the ExtMCP guardrail before reaching the client. Step 5: Configure Goose to Use agentgateway The final step is the simplest. In Part 1, Goose connected directly to the Quarkus backend: YAML # Part 1 — direct connection extensions: customer-tools: enabled: true type: http uri: http://localhost:8080/mcp headers: Content-Type: "application/json" For Part 2, change the URI to point to agentgateway: YAML # Part 2 — through agentgateway extensions: customer-tools: enabled: true type: http uri: http://localhost:3000/mcp headers: Content-Type: "application/json" Copy the updated config: YAML cp part2-agentgateway/goose-extension-config.yaml ~/.config/goose/config.yaml Now launch Goose and test the same prompts from Part 1: Plain Text Check customer status for CUST-4091 and verify health logs for their region The response is identical to Part 1, but the traffic now flows through agentgateway with JWT validation, RBAC enforcement, and guardrail inspection. You can verify this by checking the agentgateway UI at http://localhost:15000/ui — every tool call appears in the request log with its authentication status and policy decisions. The Complete Configuration Here is the full config.yaml combining all four security layers: YAML # yaml-language-server: $schema=https://agentgateway.dev/schema/config config: tracing: endpoint: http://localhost:4317 protocol: grpc sampling: parent: true default: 1.0 mcp: port: 3000 policies: cors: allowOrigins: - "*" allowHeaders: - mcp-protocol-version - content-type - mcp-session-id - authorization exposeHeaders: - Mcp-Session-Id mcpAuthentication: issuer: http://localhost:9000 audiences: - "http://localhost:3000/mcp" jwks: url: http://localhost:9000/.well-known/jwks.json resourceMetadata: resource: http://localhost:3000/mcp scopesSupported: - "mcp:tools:read" - "mcp:tools:execute" bearerMethodsSupported: - header mcpAuthorization: rules: - 'has(jwt.roles) && "operator" in jwt.roles' - > has(jwt.roles) && "viewer" in jwt.roles && mcp.tool.name in ["getCustomerStatus", "getZoneHealthLogs", "getSLACompliance"] - > has(jwt.roles) && "auditor" in jwt.roles && mcp.tool.name in ["getAuditTrail", "getSLACompliance"] mcpGuardrails: processors: - kind: remote host: "localhost:9001" failureMode: failClosed methods: tools/call: request tools/list: response targets: - name: customer-tools mcp: host: http://localhost:8080/mcp Bonus: Interactive Security Console The demo includes a browser-based SPA (index.html) that lets you visualize the entire security flow without touching the command line. The start-all.sh script serves it automatically on port 8888. Open http://localhost:8888/index.html and you'll see an enterprise-style console with: Config selector – switch between three tiered configs to see how each maps to a real deployment stage:Live stat tiles – session status, request count, tools discovered, and security checks passed/deniedAnimated architecture diagram – watch MCP requests flow from Goose through agentgateway's security layers to the Quarkus backend in real timeConfig-aware security layers – JWT, RBAC, and ExtMCP layers animate as "checking → passed" when enabled in the selected config, or appear as "skipped" with a badge when not configured ConfigUse CaseSecurity Layersconfig-dev.yamlLocal development — pure proxy pass-through for rapid iteration without security overheadNoneconfig-guardrails.yamlStaging / shared environments — blocks tool poisoning and header injection before requests reach the backendExtMCPconfig.yamlProduction deployment — full security stack with JWT identity verification, role-based tool access via CEL, and input sanitizationJWT + RBAC + ExtMCP The four demo steps — Initialize, List Tools, Call Tool, and Poison Test — make real MCP requests through agentgateway and display the JSON-RPC responses. Switching configs lets you demonstrate the difference: with config-dev.yaml, the poison test passes through unblocked; with config-guardrails.yaml, the ExtMCP guardrail catches and denies it; with config.yaml, every request also passes through JWT authentication and RBAC authorization before reaching the guardrail layer. What We Achieved Starting from the unprotected Quarkus MCP server in Part 1, we added four security layers without changing a single line of the backend Java code: LayerWhat It Doesagentgateway FeatureAuthenticationVerifies caller identity via JWT/OAuth 2.1 with PKCEmcpAuthenticationAuthorizationEnforces tool-level RBAC per rolemcpAuthorization with CELInput sanitizationBlocks tool poisoning and header injectionmcpGuardrails (ExtMCP)ObservabilityTraces every tool call through the proxyOpenTelemetry integration The Quarkus MCP backend remains a clean, focused tool server. All governance concerns live in the agentgateway configuration and the Quarkus gRPC guardrail service — keeping the entire stack in Java, exactly where platform engineers expect to find them. What's Next: Part 3 In Part 3: End-to-End Tracing and Observability Across Goose, agentgateway, and Quarkus, we'll wire up distributed tracing across the full agent traffic path. You'll see how a single Goose prompt generates a trace that spans the agent, the gateway, and the Quarkus backend — with tool-call latency, RBAC decisions, and guardrail verdicts all visible in a single Jaeger or Grafana Tempo timeline. We'll configure OpenTelemetry exporters in all three components and build a Grafana dashboard that gives platform teams real-time visibility into their agentic infrastructure. Stay tuned. More
Pipelines on Fire: Why Your CI/CD Tools Are the New Cyber Battlefield
Pipelines on Fire: Why Your CI/CD Tools Are the New Cyber Battlefield
By Igboanugo David Ugochukwu DZone Core CORE
Your Email Security Is a DNS Configuration Problem
Your Email Security Is a DNS Configuration Problem
By Vadim Ivanov
The Bottleneck of Scaling
The Bottleneck of Scaling
By Vishal Bhatia
Gossip on Cryptography: Part 3
Gossip on Cryptography: Part 3

In this blog, we will continue our discussion from the previous blog, Parts 1 and 2. If you have not read it, please read it once. So far, we have discussed Caesar cipher, Vigenere cipher, symmetric encryption, AES, convergent encryption, and IV. If all these terms sound familiar to you — great! If not, please go back and read Part 1 and Part 2 first. Now, in Part 2, we ended with a teaser that in the next blog we will talk about hashing and asymmetric algorithms. So let's get into it! First, Let's Talk About Hashing So far, everything we discussed was about encryption and decryption — you encrypt something, and you can decrypt it back. Simple. But what if I tell you there is a technique where you convert data into something, and you can NEVER go back to the original? Sounds weird, right? Why would someone do that? Let me give you a real-life example. Imagine you are the owner of a hostel. You keep a register at the gate. Every night at 10 PM, you take a photo of this register. Now, the next morning, if someone modifies the register (adds a fake entry or removes one), you can easily compare yesterday's photo with today's register and catch the change. Hashing works exactly like this photo. You give any data as input; the hash function produces a fixed-size string (called a hash or digest). If even ONE character in the original data changes, the hash output changes completely. A Simple Example Input: "Sahil" Hash (SHA-256): 9b4c...a32f (a 64 character string) Input: "sahil" (just lowercase 's') Hash (SHA-256): 7f3a...b91e (a completely different 64-character string!) See? Even one small change → completely different hash. This property is called the Avalanche Effect. Important Properties of Hashing Let's keep it simple. A good hash function has these properties: One way (Irreversible): You can go from "Sahil" → hash, but NOT from hash → "Sahil." It's a one-way street. Like making an omelet from an egg — you can't get the egg back from the omelet.Deterministic: The same input will ALWAYS give the same output. "Sahil" will always produce the same hash every time.Fixed size output: No matter how big your input is — whether it's one word or an entire 1000-page book — the output hash size is always the same (for SHA-256, it's always 64 characters).Avalanche effect: Even a tiny change in input means a completely different hash. We just saw this above. So, Where Is Hashing Used? Great question! Here are the most common places: 1. Storing Passwords This is the most common use case. When you set a password on any website, good websites never store your actual password. They store the hash of your password. So when you log in next time: You type your passwordThe website hashes itCompares it with the stored hashIf they match → Welcome! This is why when you click "Forgot Password" on most websites, they reset your password instead of showing you the old one. Because they literally don't know what your old password was! 2. File Integrity Check You download software from the internet. How do you know no one tampered with it during download? The website gives you the hash of the original file. After downloading, you calculate the hash of your downloaded file. If they match, the file is safe! This is used everywhere — Linux ISO downloads, software releases on GitHub, etc. 3. Digital Signatures We will cover this more in detail in coming parts! Popular Hashing Algorithms MD5 – Old, fast, but now considered weak. Avoid using it.SHA-1 – Also old, mostly deprecated now.SHA-256 – The current gold standard. Used everywhere. (Bitcoin also uses this!)bcrypt/Argon2 – Special hashing algorithms designed specifically for passwords. They are intentionally slow — which makes brute force attacks harder.PBKDF2 (Password-Based Key Derivation Function 2) – Another password-specific algorithm. It takes your password + a salt and runs a hashing function thousands of times in a loop (this is called key stretching). The more iterations, the harder it is to brute force. It is widely used and is the recommended choice in many government and enterprise security standards (like NIST). Wait — Can Someone Still Crack Hashes? Yes! There are ways to try. The most common one is called a Rainbow Table Attack. Here's how it works — imagine I am a hacker and I have pre-calculated the hashes of millions of common passwords: "password" → 5f4dcc..."123456" → e10adc..."admin" → 21232f... Now if I get your stored hash from a database breach, I just look it up in my table. If your hash matches any entry → I know your password! Solution? SALT! No, not the one you put in food In cryptography, a Salt is a random value that is added to your password before hashing. Your password: "mypassword" Random Salt: "xK9#mQ" Combined: "mypasswordxK9#mQ" Hash of this: (some unique hash) Now, even if two people have the same password "mypassword", because their salts are different, their stored hashes will be completely different! Rainbow Table attacks become useless. The salt is stored alongside the hash in the database (it's not a secret; it just needs to be unique per user). Now Let's Talk About Asymmetric Encryption Remember in Part 2 we discussed symmetric encryption — where the same key is used for both encryption and decryption? The problem with symmetric encryption is — how do you share the key safely? Imagine Rahul in Delhi wants to send an encrypted message to Priya in Mumbai. He needs to share the key with her first. But if he sends the key over the internet, a hacker can intercept the key and then decrypt all future messages. This is known as the Key Distribution Problem. Asymmetric encryption solves this beautifully. The Magic of Two Keys In asymmetric encryption, instead of one key, you have two keys: Public key – You share this with the WHOLE WORLD. Anyone can have it.Private key – This stays with you ONLY. Never share it with anyone. The magic is: Whatever is encrypted with the Public Key can ONLY be decrypted with the Private Key. And these two keys are mathematically linked to each other. Real Life Example — The Magic Mailbox Think of it like a special mailbox: The mailbox has a slot (public key) — anyone can drop a letter in it.But only YOU have the key to open the mailbox (private key) — only you can read the letters. Rahul wants to send a secret message to Priya: Priya shares her Public Key with Rahul (and the whole world — no problem!)Rahul uses Priya's Public Key to encrypt the messageThe encrypted message travels over the internet — even if a hacker intercepts it, they can't read itPriya uses her Private Key to decrypt the message No need to share any secret key beforehand! The problem of key distribution is solved! Most Popular Asymmetric Algorithm: RSA RSA (named after its inventors Rivest, Shamir, and Adleman) is the most famous asymmetric algorithm. It is based on a very simple mathematical observation: It is very easy to multiply two large prime numbers. But it is extremely hard to factorize the result back into those two primes. For example: Easy: 61 × 53 = 3233Hard: Given 3233, find the two prime factors (61 and 53) When the numbers are hundreds of digits long, even the fastest computers in the world would take millions of years to crack it. That's the security of RSA! RSA key sizes you will commonly see: 1024-bit (old, avoid), 2048-bit (current standard), 4096-bit (extra secure). Symmetric vs. Asymmetric — When to Use What? Symmetricasymmetric Keys Same key for encrypt & decrypt Different keys (public + private) Speed Very Fast Slow Key Sharing Problem Yes, it exists No, solved! Example Algo AES RSA Used For Encrypting large data Key exchange, Digital Signatures In the real world, both are actually used together! The typical flow is: Use asymmetric encryption to securely exchange a secret keyThen use symmetric (AES) encryption for the actual data — because it's much faster This combo is how HTTPS (the secure web) actually works! When you open any https:// website, this exact thing is happening in the background. That little lock you see in your browser? That's this. Terms We Have Learned So Far (Including Parts 1 & 2) CryptographyAlgorithmPlain textKeyCipher textSymmetric encryptionConvergent encryptionInitialization vector (IV)HashingHash/DigestAvalanche effectSaltRainbow table attackAsymmetric encryptionPublic keyPrivate keyRSA Please keep them in mind, as these are the generic terms used everywhere in the world of encryption and decryption. Coming in Part 4 (Part 4 is in progress — stay tuned!) In the next blog, we will gossip about some very interesting things like: Digital signatures – How do you prove that a message is really from who it claims to be from?PKI infrastructure – The backbone of trust on the internetSSL/TLS – What actually happens when you open an HTTPS website, step by stepEnvelope encryption – A very clever technique used by cloud providers like AWS and GCPAnd more... Stay tuned for Part 4! If you liked this blog, do give it a like and share it with someone who you think should learn this. Let's spread the knowledge! Read the previous parts here: Part 1 and Part 2.

By Sahil Aggarwal
Beyond Agent-Washing: The Engineering Principles Behind Production-Ready AI Agents
Beyond Agent-Washing: The Engineering Principles Behind Production-Ready AI Agents

An AI agent is not defined by how intelligently it talks. It's defined by what it's trusted to do. Give a language model a chat window, and you have an interface. Give it access to production APIs, identity, business logic, memory, and the authority to execute actions on your behalf, and you have something categorically different: a new kind of software actor, one that can read your data, write to your systems, and make decisions faster than any human reviewer can watch in real time. That distinction is where most of the industry's "agent" marketing quietly falls apart. And in 2026, the gap between marketing and engineering has become measurable. AvePoint's third annual State of AI report, based on 750 global IT leaders across financial services, healthcare, and government, found that 88.4% of organizations experienced at least one AI agent–related security breach in the past twelve months, with data leakage and manipulation by untrusted input as the two leading causes (AvePoint, State of AI 2026). Separately, McKinsey's 2026 AI Trust Maturity Survey — fielded across roughly 500 organizations between December 2025 and January 2026 — found that only about 30% of organizations have reached a mature level of governance and agentic controls (McKinsey, State of AI Trust 2026). Adoption has outrun governance by a wide margin, and the bill is starting to come due. I spent several hours this year with Ted Kornish, CTO of Gravity, a platform built for corporate sustainability and emissions reporting, to press on a narrower question than "does your product have an agent?" The question was: what has to be true, architecturally, before a system earns the word at all? Kornish's answers are the evidence in this piece, not the subject. Gravity is a useful case study — a production system operating in a regulated domain where a wrong answer can end up in front of a regulator — but the principles below are my own synthesis of what separates a genuine enterprise agent from a chatbot wearing a lanyard. The Five-Question Agent Test Before accepting any vendor's "AI agent" claim — including Gravity's — I now run it through five questions: Can it execute actions, not just generate recommendations?Can it compose multiple capabilities to accomplish a goal it wasn't explicitly scripted for?Does it operate through the same business logic and APIs your human users already use?Can every action it takes be attributed to a specific identity and audited after the fact?Can a potentially destructive action be previewed, validated, and approved before it commits? If the answer to the first three is no, you're looking at an AI feature — useful, maybe, but bounded and predictable. If the answer to four and five is no, you're looking at an AI feature that probably shouldn't have production access yet, regardless of how capable its model is. Kornish's version of this test, from our conversation, is the cleanest I've heard a vendor articulate unprompted: "If you can list every task it handles, it is a feature. If you can't because it can compose a wide variety of platform capabilities, it's an agent." Ask a vendor to enumerate every task their agent handles on a sales call. Most stall out after three or four items, and the stall itself is diagnostic — a genuine agent's capability surface resists a clean list because it's compositional, not because nobody documented it. Why API-First Architecture Is the Real Moat Here's the engineering claim underneath the marketing claim, and it's the one I'd stake the rest of this article on. Most enterprise platforms were built UI-first, with an API bolted on afterward as a partial mirror of what the interface already does. That ordering was fine for a decade of humans clicking buttons through a screen. It becomes a structural liability the instant an agent needs to act with the same range a human employee has — because if the API was never built to be complete, an engineering team is stuck choosing between two bad options: let the agent do less than a human could, or build a second, parallel execution path just to give it a fighting chance. Neither ages well. The first caps the agent's usefulness permanently. The second means maintaining two versions of every business rule indefinitely, with near-certainty that they drift apart in some edge case nobody thought to test. Gravity sidestepped that fork by never building a second path: "We never built a separate 'agent version' of Gravity with its own private set of functions. On the read path, the agent calls the exact same HTTP API our product UI calls... On the write path, there's a service layer that provides one execution path for any given action, one place validation and business rules live, and one codebase to test and maintain instead of two." The architectural implication here is bigger than agent convenience. Once an agent becomes just another API client, the API itself becomes part of the agent's safety boundary. Authentication, authorization, idempotency, validation, rate limiting, transaction handling, and auditability stop being secondary infrastructure and become prerequisites for autonomy. That reframes the first question an engineering team should ask when evaluating agent readiness. It isn't "which model should we use." It's closer to: can an untrusted software actor safely exercise the application capabilities we already have? Kornish's line on this is worth sitting with: "An agent is really just a new kind of caller on an API that was already capable; if that API doesn't exist, there's nothing for the agent to stand on." That's not a race to fine-tune the flashiest model. It's a race to have quietly built a serious, typed, permissioned API years before anyone had a reason to think an LLM would ever call it — and most incumbent platforms already lost that race without knowing it, because the decision was made a decade ago by a team with no reason to think about agents at all. The hardest part, in his account, isn't reads. Reads are comparatively low-stakes; nothing breaks if a summary is slightly stale. Writes are a different category of problem entirely, because most enterprise write paths aren't atomic — you're editing individual records one at a time, and the only thing worse than writing the wrong data is writing partial data, which leaves the system in a state nobody designed for and nobody can cleanly roll back. That's the real, unglamorous reason so many "enterprise AI agents" on the market today are read-only interfaces wearing an agent's branding: atomic writes are hard, and most legacy write paths were never built for it. What This Looks Like In Production Abstract architecture arguments are easy to nod along to and hard to actually picture. Here's a concrete workflow, reconstructed from how Gravity describes its own execution path for a regulatory reporting task: Plain Text User: "Prepare this quarter's emissions report and flag anything that needs my attention." ↓ Agent — parses the objective, checks its ~85 versioned skills for the relevant reporting operations (progressive disclosure: hand the model what's relevant to this step, not everything) ↓ APIs — retrieves source data through the same HTTP API the product UI uses, governed by the same permissions ↓ Deterministic Engine — runs the actual emissions math; the model never touches the calculation itself ↓ Validation — checks for anomalies, missing fields, and inconsistencies against prior filings ↓ Agent — drafts the report narrative, cites every source and every calculation it pulled from ↓ Human — reviews the staged preview, approves or rejects ↓ Production — the report commits; the full trace is retained The model's job in that chain isn't the arithmetic — it's everything around the arithmetic. Sourcing the right document, filling a gap, matching the tone of last year's filing so the report reads as one continuous document rather than two authors stitched together. The ground truth stays boring and deterministic. The model's intelligence gets spent on the parts that used to consume a compliance team's entire week. Agent vs. Copilot vs. Automation The three categories get flattened together constantly, and the flattening is exactly what lets the word "agent" get retrofitted onto anything with a chat box. CapabilityCopilotAutomationAI AgentGenerates contentYesSometimesYesFollows a predefined workflowYesYesYesHandles goals it wasn't explicitly scripted forLimitedNoYesComposes tools/capabilities dynamicallyLimitedNoYesExecutes production actionsLimitedYesYesRequires human approval on risky actionsUsuallySometimesShould, by designMaintains task state across sessionsLimitedYesYesAdapts execution mid-taskLimitedNoYes These categories overlap in practice more than the table suggests, and that's worth saying plainly: the meaningful distinction isn't the marketing label a vendor chose; it's the system's actual degree of autonomy, compositionality, and execution authority. The Five Layers, and Why They Aren't Interchangeable Looking at Gravity's stack alongside broader enterprise agent design patterns, a consistent structure emerges — five layers, each dependent on the one below it, each a distinct point of failure if it's missing or built poorly. Plain Text Layer 5 — Governance Identity · audit · human approval ↑ Layer 4 — Execution Layer The API business logic actually runs through ↑ Layer 3 — Domain Skills Versioned, tested, maintained like production content ↑ Layer 2 — Planning & Reasoning Goal decomposition, task checklists ↑ Layer 1 — Foundation Model Replaceable, increasingly commoditized My interpretation of this stack is that the layers are not equally substitutable. The foundation model is replaceable — most serious vendors have access to roughly the same handful of frontier models, and Layer 1 is where competitive advantage is evaporating fastest. The planning harness can evolve; skills can be versioned and rolled back like any other content. But the execution and governance layers are much harder to swap out, because they encode an organization's actual operating rules — its permission model, its validation logic, its regulatory obligations. That suggests the durable competitive advantage in enterprise agents sits lower in the stack than most of the AI discourse right now assumes. Everyone is fighting over Layer 1. Most of the actual failures live in Layer 4 and Layer 5 — the parts that don't show up in a demo. Kornish's own framing on where the industry is spending its engineering effort was more candid than I expected from a CTO on the record: "we're very bitter-lesson-pilled over here: increasingly, the agent is just a model and a thin harness... the system prompt is getting shorter over time as the models internalize more capabilities." The elaborate custom orchestration graphs that dominated agent architecture discussions for the last two years are being displaced — thinner harnesses, base models absorbing more of that responsibility natively, and open protocols like Model Context Protocol doing at the ecosystem level what bespoke orchestration used to do inside one vendor's codebase. My Technical Take: The Agent Is Only As Strong As Its Execution Boundary Here's the architectural lesson I actually take from this, stated plainly and separately from anything Kornish said: an enterprise agent shouldn't be designed as an intelligent application sitting on top of an existing platform. It should be designed as an execution client operating inside a security and transaction boundary that already exists independently of it. That distinction sounds pedantic until you trace what happens when it's missing. If a model can reason but can't safely execute, it's a copilot — useful, bounded, not what we're talking about in this piece. If it can execute but bypasses the platform's own authorization and validation logic to do so, it's not an agent, it's a security liability wearing an agent's branding. If it can execute safely but nothing about that execution is observable after the fact, it's operationally untrustworthy regardless of how well it performed in the room. And if it clears all three of those bars but is never re-evaluated as the model, the skills, and the surrounding data shift, its reliability will decay quietly, without anyone noticing until an incident forces the question. That chain leads to a fairly simple principle, and it's the one I'd want any engineering team to write on a whiteboard before they start: the model should decide what to attempt. The platform should decide what is allowed to happen. Collapse that distinction — let the model's judgment double as the authorization check — and you don't have an agent. You have a very articulate way of bypassing your own access controls. I'd formalize this as the agent execution boundary — the layer, architecturally distinct from the model itself, where a proposed action gets checked against identity, authorization, validation, deterministic logic, and (ideally) a dry run, before it's ever allowed near production data: Plain Text USER INTENT │ ▼ ┌─────────────────────┐ │ AI MODEL │ │ Reason/Plan │ └──────────┬──────────┘ │ ▼ ┌─────────────────────┐ │ AGENT SKILLS │ └──────────┬──────────┘ │ ▼ ┌─────────────────────────┐ │ EXECUTION BOUNDARY │ │ │ │ Identity │ │ Authorization │ │ Validation │ │ Deterministic Logic │ │ Dry Run │ │ Audit │ └────────────┬────────────┘ │ Human Approval │ ▼ PRODUCTION DATA The model should never be the final authority on whether an action is valid. It proposes; the execution boundary determines whether the proposal is permitted, reversible, and safe to commit. Everything Gravity described to me — the shared API surface, the dry-run engine, the staged preview, the permission-inherited approval gate — is one implementation of that boundary. It isn't the only valid implementation. But any architecture that skips having one, in favor of trusting the model's own judgment about what's safe, is building on a foundation that will eventually fail in a way nobody can cleanly attribute afterward. Deterministic vs. Probabilistic Is the Real Design Boundary The design decision that matters most isn't "AI versus not AI." It's probabilistic versus deterministic execution, and a serious architecture puts uncertainty exactly where judgment is valuable and removes it everywhere correctness is mandatory. Kornish didn't hedge when I asked him where Gravity draws that line: "Emissions math always runs through our deterministic calculation engine, no exceptions, because a regulator does not accept 'the model recalculated it slightly differently this time' as an answer." His broader rule generalizes well past sustainability reporting into any regulated domain — healthcare, finance, insurance: where correctness counts, use a calculator; where you need judgment, use a person; where approximate correctness is fine, AI saves time and money. The model can decide which document to inspect, which skill to invoke, and how to explain an anomaly in plain language. It should not silently become the source of truth for a number that could have been deterministically reproduced. Any output that could become evidence in an audit, a regulatory filing, or a legal dispute should trace back to a deterministic calculation — never a model's best guess, no matter how good that guess usually is. "Usually" is not a standard a regulator will accept, and it shouldn't be a standard engineering teams accept for themselves either. What Gravity Gets Right — And Where I'd Push Back To be specific about what Gravity's architecture actually demonstrates, rather than just asserting it's good: API-first execution with no parallel agent path, a deterministic calculation engine that the model cannot override, permission inheritance instead of a separate AI policy layer, staged writes with mandatory human approval, persistent task state across sessions, a visible reasoning trace and tool-invocation history, and continuous evaluation fed by production telemetry rather than a one-time launch gate. Gravity isn't interesting because it has an AI agent. What's interesting is the infrastructure surrounding the agent — the model is one component of the system; the API, the identity model, the deterministic engine, and the governance layer are what determine whether that model can be trusted to act in the real world at all. That said, don't mistake this for an endorsement without edges. A few things I'd want to see stress-tested before calling any system like this "solved," Gravity included: how the skill-versioning process holds up under a genuinely adversarial red-team exercise, not just replay against known scenarios; whether the "agent inherits operator permissions" model actually closes the gap on authorized-but-unintended actions (more on that below); and how the observability stack performs under a long-running, multi-day task where context has had real time to drift. None of these are unique to Gravity. They're unresolved for the industry broadly, which is exactly why they're worth naming instead of glossing over. Identity Is the Security Boundary — But It Isn't the Whole Boundary This is where I'd ask security-minded readers to slow down, because the industry's threat model for agents is still catching up to what's actually being deployed in production right now. Kornish's framing — treat the agent as a new employee with its own login, not a master key that opens every door in the building — maps directly onto zero trust: never assume implicit trust from network location or system role, verify explicitly on every call, grant only the minimum privilege the task requires. "Every action carries the operating user's identity through the entire call chain," he told me, "so when the agent hits our API, it's authorized exactly the way that user would be, not under some elevated service account." Permissions get recalculated on every request rather than cached at login — cached permissions are exactly the kind of stale-state bug that turns into an incident report months after the access was actually revoked. From a security engineering perspective, this changes the traditional question of "what can the agent access" into a more useful one: under whose authority is this specific action being performed? That distinction matters because it should follow the action through the entire call chain, not just the login event. But identity and least privilege, however well implemented, are necessary and not sufficient — and 2026 has produced hard evidence of exactly where that gap sits. In June 2025, researchers at Aim Security disclosed a zero-click prompt injection against Microsoft 365 Copilot, later assigned CVE-2025-32711 with a CVSS score of 9.3 and nicknamed EchoLeak. A single crafted email, no user interaction required, and Copilot followed hidden instructions embedded in the email to pull data out of OneDrive, SharePoint, and Teams (Beam AI, 5 Real AI Agent Security Breaches in 2026). The exploit didn't need a broken permission model. Copilot was, in a narrow technical sense, doing exactly what it was authorized to do — retrieve and summarize data the user could already access. It was the intent behind the action that was compromised, not the authorization. That's the distinction the OWASP GenAI Security Project formalized when it published the Top 10 for Agentic Applications in December 2025, ranking Agent Goal Hijack as ASI01 — an attacker redirecting an agent's objective through content it reads, rather than code it runs, so the agent pursues the attacker's goal while believing it still serves the user's (Cycode, OWASP Top 10 for Agentic Applications 2026). There's an important line between authorization failure and intent failure. Traditional security controls are built to prevent unauthorized actions. Agent security has to address a class of failure traditional AppSec never had to model: an authorized action performed for an unauthorized purpose. A compromised document can convince an agent to delete a record the operating user genuinely has permission to delete. The API correctly authorizes the request. The system is still compromised. Simon Willison's "lethal trifecta" is the cleanest mental model I've seen for when this actually turns dangerous: access to private data, exposure to untrusted content, and the ability to communicate externally. None of the three legs is a vulnerability in isolation. It's the combination that creates the exposure — an attacker slips an instruction into content the agent will process, the agent executes it, private data leaves the perimeter (Getia Consulting, AI Agent Security 2026). This isn't a theoretical framing anymore. Check Point Research documented a single operator combining Claude Code and GPT-4.1 to breach nine Mexican government agencies between late December 2025 and mid-February 2026, converting roughly 1,088 typed prompts into more than 5,300 AI-executed commands and exposing on the order of 400 million records — tax filings, civil registry, patient, vehicle, and electoral data (awesome-ai-agent-attacks, GitHub). That incident wasn't a prompt injection against a victim's agent — it was an attacker using agentic tooling as their own offensive platform — but it's a preview of the asymmetry defenders are up against: the same architecture that makes an agent useful for legitimate multi-step work makes it useful for an attacker's multi-step work too. The supply chain is its own exposure. In March 2026, a malicious package sat live on PyPI for roughly three hours — the compromised LiteLLM release, which serves as the model gateway for CrewAI, DSPy, Microsoft GraphRAG, and a long list of other agent frameworks — during which roughly 47,000 downloads occurred, pulling a backdoored autonomous attack tool in alongside the update (Help Net Security, June 2026). That maps to ASI04 in OWASP's taxonomy — agentic supply chain vulnerabilities — a category that barely existed as a named risk eighteen months ago. This is why identity propagation, least privilege, and audit trails are necessary but not sufficient on their own. Agent security also needs constrained tool semantics, confirmation boundaries on irreversible actions, data provenance the agent can actually answer for, and evaluation against adversarial instructions as a standing practice, not a pre-launch checklist item. Observability as an Accountability Mechanism, Not a Debugging Aid I'd push this argument one step further than the industry currently takes it: an agent's execution trace should be treated as a first-class security artifact, not an engineering convenience. A traditional distributed trace tells you where a request traveled. An agent trace needs to answer a different set of questions — why the system selected a given tool, which identity authorized the action, what data informed the decision, what parameters were supplied, what the tool returned, and whether a human actually approved the resulting write. Gravity's implementation — a visible execution checklist that doubles as a timeline, a reasoning trace per step, full tool-invocation history, and a staged preview of every proposed change before commit — is the same instinct that produced distributed tracing and OpenTelemetry in cloud-native infrastructure a decade ago: a system too complex to reason about from the outside has to narrate its own behavior from the inside, continuously, or nobody downstream trusts it under real load. That reframing — trace as accountability mechanism rather than debugging tool — is, in my view, the more consequential shift, because it's what turns an incident review from forensics into something closer to a routine audit. The Hard Problems That Remain None of the above should read as "solved." The honest list of what's still genuinely unresolved, industry-wide, in mid-2026: Prompt injection, both direct and indirect, remains structurally difficult to fully close — OWASP's own 2026 LLM Security research put the year-over-year surge in injection attempts at 340% (AI Magicx, April 2026).Memory poisoning — a persisted context corrupted so a later, unrelated task inherits false assumptions — is now its own OWASP category (ASI06) and doesn't map cleanly onto any pre-LLM threat model.Authorized-but-unintended actions, the EchoLeak pattern, aren't fixed by permission inheritance alone.Long-running task failures and context drift over multi-day agent sessions are still mostly evaluated in demos, not adversarial production conditions.Skill and harness regressions can silently degrade a capability customers were actively relying on, which is why versioning and rollback matter as much for skills as for any other production code.Evaluation at scale — proving an agent's judgment holds up across the long tail of messy, real scenarios rather than a fixed benchmark set — remains closer to an open research problem than a solved engineering one. The goal with each of these isn't to eliminate the risk outright. It's to make it observable, bounded, testable, and recoverable — which is a materially different, more honest bar than "safe." My Framework for Evaluating Enterprise Agents After going through Gravity's architecture in this level of detail and cross-referencing it against the broader 2026 threat and adoption data, I'd reduce production readiness to seven questions: Agency – Can the system independently execute multi-step objectives, not just suggest them?Compositionality – Can it combine capabilities it wasn't explicitly wired into a fixed workflow to perform?Execution – Does every action pass through the same validated business logic a human user would hit?Identity – Can every action be attributed to a specific principal, recalculated per call rather than cached?Determinism – Are correctness-critical operations owned by a deterministic system, not a model's best guess?Governance – Can risky mutations be previewed, validated, approved, and audited before they commit?Evaluation – Can the system demonstrate its behavior stays reliable as models, skills, and scenarios keep shifting underneath it? If an agent fails several of these, improving the model is very rarely the first engineering problem worth solving. Where This Is Headed Kornish's predictions for the next three to five years weren't flashy, which is exactly why I'd take them seriously. Scope stays the whole story — agents confined to what they were demoed on keep losing ground to agents that can operate across an entire platform, the same pattern that played out when narrow point solutions gave way to platforms in every other software category. Vendors without an API-first foundation fall further behind every year, because that kind of scope isn't something you retrofit after the fact. Gartner's own projection points in the same direction: 40% of enterprise applications are expected to embed task-specific AI agents by the end of 2026, up from under 5% in 2025 (Paul Okhrem, Enterprise AI Agents Adoption Statistics 2026). And observability finishes its transition from differentiator to baseline expectation, tracing the same arc logging and monitoring took in cloud infrastructure roughly a decade ago. The industry's conversation about enterprise AI has spent the last few years almost entirely on model capability. The next phase is being decided somewhere else — in the API surface, the identity model, the deterministic core, and the governance wrapped around every write. That's not a glamorous place for the conversation to move. It's also the only place it was ever going to survive contact with a regulator, an auditor, or an attacker. I conducted this interview with Ted Kornish, CTO of Gravity, specifically to understand how these architectural questions are being resolved inside a production system operating in a regulated domain. His platform is the case study here; the analysis and framework above are my own.

By Igboanugo David Ugochukwu DZone Core CORE
Designing a Dynamic Multi-Hierarchy Security Model for Analytics and Decision Support Systems
Designing a Dynamic Multi-Hierarchy Security Model for Analytics and Decision Support Systems

A simple access check uncovered something alarming: several dashboards still showed employee compensation based on an organizational hierarchy that was no longer relevant. Our row-level security framework stopped synchronizing because of a Workday HCM API timeout issue, but meanwhile, the entitlements on those dashboards did not get adjusted to reflect the new organization setup. No permission changes were made. Despite changes in the hierarchy, the access layer was unable to adapt to the changes. This is the reason why this project became necessary. First of all, we completely redesigned the access layer that sits behind all BI tools, Adaptive Planning models, and Snowflake shares used by our FP&A and risk departments. It was done not using any static table with roles, but with the help of tracking constantly changing hierarchies of organizations, cost centers, legal entities, and products that include deal closures and divisions' separations. Let me explain how it was accomplished: first, I will describe the process of keeping hierarchies up-to-date in the ingestion layer; then I will explain the visibility engine and its policies of providing access at a row level. Finally, I will show how to keep access scopes the same in Power BI, Tableau, and Adaptive Planning. Overview The system obtains hierarchical information from Workday HCM, Salesforce, and the internal table of the legal entities in order to reconcile these three conflicting lists into a single tagged entity graph. Visibility between users and the entities is calculated upon any change of the hierarchy edges, and not after a periodic re-access review as in previous attempts. The visibility enforcement occurs on the Snowflake row level, guaranteeing that Power BI, Tableau, and custom SQL queries obtain the same limited results set, which helps avoid discrepancies because of individual permission tables per tool. Consistent entity scope is provided for the Workday Adaptive Planning security groups; as a result, there will be no mismatches between allowed viewers of the planning sheets and the dashboards. Orphan nodes in the hierarchy are identified before their contribution to visibility gaps appears. Components The framework is made up of four main parts: hierarchy ingestion layer, dynamic security mapping engine, Snowflake row-level access control, and synchronization layer that relays outcomes to BI and planning software applications. These parts produced practical insights during difficult experiential learning. Hierarchy Ingestion Layer Four sources have been used for this hierarchical workflow. They include Workday HCM organizational hierarchy and cost centers hierarchy, location level hierarchy that groups individual branches/plants/facilities according to regions, account hierarchy of Salesforce accessed using the Bulk API and a legal entities reference table maintained manually in Snowflake. The four hierarchical sources have been transformed into a single closure table having fields as entity_id, parent_id, hierarchy_type, and effective_date. While one of the first considerations was to use only the organizational hierarchy in Workday HCM, the reason was mainly because of its completeness. But it is simply impossible to rely on just this hierarchy because there could be instances where a single cost center reports itself to two legal entities due to allocation of services. Additionally, the location-level hierarchy of any branch does not have anything to do with the organizational hierarchy position of its manager – it could be a branch within one region reporting to a manager in another region. So, maintaining each hierarchy separately and ensuring that configurations are retained helps. One of the difficult aspects of this entire process was the fact that a complete extract of the Workday HCM hierarchy was extremely slow, such that it caused issues with the synchronization to adaptive planning. To avoid this problem, it was necessary to implement change detection instead. Dynamic Security Mapping Engine This process goes through the closure table starting at each user’s home node and translates all the descendant entity_ids into a flatter structure of USER_ENTITY_ACCESS, along with the effective dates. Key to the design is the fact that a user’s home node is not a simple identifier but a collection of (user, hierarchy_type, home_node). Thus, the same user can have a User + Location Hierarchy as well as a User + Organization Hierarchy, each handled in the same fashion. Access was previously managed through manually maintained grants in Snowflake for each business unit. That worked fine until some organizational change would occur, at which time many grants had to be updated. Moving to the recursive resolution from a single source-of-truth hierarchy allowed this to become unnecessary; there is no grant to maintain when a cost center moves. We found that the effect of one edge change could be more far-reaching than expected. As an example, when a cost center moved to a different regional vice president, the map engine would do a traversal of the closure table starting at this node and then down the whole chain and then write out all the affected USER_ENTITY_ACCESS entries for anyone who mapped back to this node. In one typical move, over two thousand downstream entries in the USER_ENTITY_ACCESS table were affected by changing just one edge in the hierarchy. To solve this problem, the original map engine did a full recalculation of all the accesses in the table for each pass through the process. This was a brute force solution but one that put enough of a strain on the system that it locked up the table in business hours. The second map engine solves this issue by updating only the affected subtree. Foe example, one engine solves any hierarchy, not just one. Think about a regional operations manager. Their User + Location Hierarchy assignment specifies the regional level home node, meaning that all locations below that region within the location hierarchy become part of their assignment – all branches and facilities belonging to that region. The User + Org Hierarchy assignment is a distinct and separate home node, further down the chain: exactly their own team and subordinates within it because their responsibility is not over all employees within that region but those reporting to them directly. Both assignments live in the same USER_ROLE_ASSIGNMENT table, distinguished only by hierarchy_type. They are both solved by the very same recursive procedure. Start from the home node, go over all descendants in that hierarchy chain within the closure table, and write into the USER_ENTITY_ACCESS table. This technique also allows modeling of hierarchies that do not exist yet – say, Product Line hierarchy – because we simply add another hierarchy type to the closure table, specify a home node for it, and the same engine will recognize it on next runs. Snowflake Row-Access Enforcement In Snowflake, a policy is added on the fact tables: GL detail, planning actuals, and HR costs, joining them against USER_ENTITY_ACCESS by the current session’s user ID mapped into the tenant. Hence, irrespective of who queries the data using what software tool, the result set will be automatically filtered for authorized rows only. Another option considered included creating a secure view per each business unit, running into the centralized control plane problem described by GFT's Azure Synapse Analytics – New Insights Into Data Security on DZone. The view-per-unit solution would not scale above dozens of units and required having a different Power BI dataset for each view. Adding a single row-access policy allowed setting up proper restrictions at the table level without making any changes in consumer applications. The design principle used for access restriction at runtime based on an explicit row filter by identity context and not through application-layer WHERE clauses is not exclusive to Snowflake. Another example of implementing similar logic in PostgreSQL by means of its native row-level security and session-wide tenant identifier can be found in Multi-Tenant Data Isolation and Row Level Security on DZone. Even though the approach differs from a closure table self-join, the idea behind it is the same: enforce data security in the database itself, not in the application code of consumers. A technical problem emerged with the initial policy function that used entity codes. Once Finance renamed a number of those, some of the codes were retained in cached data extracts and silently caused access restrictions due to missing codes. Therefore, a surrogate keys level was implemented to avoid invalidating already computed access rights after renames. For instance, the process illustrates that a certain policy works consistently throughout all tiers of roles: simply ask the fact table by means of an arbitrary user in the corresponding role tier and compare the entity count with the scope for that particular tier. Cost center managers, for example, would not have any access beyond their own cost center, which includes only the subordinates of that cost center; access beyond those two scopes would be considered a flaw in the policy, and not a change in the business itself, unless proven otherwise. BI and Planning Sync Layer The sync process takes USER_ENTITY_ACCESS and passes it to three recipients: Power BI Row-Level Security (RLS) role membership, Tableau user filters, and security groups of Workday Adaptive Planning. This process guarantees the matching of the access control scope between a planning sheet and a dashboard built using the very same data. In the past, each BI team kept the maintenance of its RLS roles separate. Thus, there was an inherent possibility of the situation described in the introduction of this document, when Power BI roles were delayed compared to the Snowflake policy, causing the misalignment period and the related risks. The centralization of all pushes into one mapping table reduced the likelihood of such an incident. One issue came up during the development phase of the project because of the need for trial-and-error solutions. Namely, the Adaptive Planning API sets the rate limit for security group updates, making the mass updates impracticable. The successful implementation of the batch update method, where two hundred users could be updated in one request, became evident after trying the five hundred per request solution and failing because of the throttle errors. Prerequisites Read access to Workday HCM reports' web service, restricted by organization and using a special service account.Profile-level access to Salesforce bulk API that queries account hierarchy fields.Snowflake database edition enabling row access policies, along with a role capable of creating and attaching them.Credentials for Workday Adaptive Planning Integration API, with write access to security groups.Python version 3.10 or later, the snowflake-connector-python library, and job orchestration, where we used a scheduled container, but any other scheduler could have done the job.A concise list of hierarchy types required for the job. We had wasted much time dealing with hierarchy types nobody used further downstream. Overall, the flow is straightforward and linear – hierarchy sources are responsible for populating the closure table, which gets flattened by the mapping engine to produce the access table, followed by enforcing it through row access policy and expanding the scope through the sync layer. Figure 5 illustrates the entire process briefly. Design issues that continually arise in the context of such modeling include where each hierarchy comes from, and how an organizational level maps to a set of defined entities. These are two questions that can better be addressed by example. For instance, choosing a hierarchy graph vs. choosing a tree graph: The organizational level hierarchy describes supervisory reporting lines within Workday HCM, in which an individual contributor reports to a manager, who in turn reports to a director, and so on, independent of their actual geographical location. The cost-center hierarchy can be maintained within Workday HCM but follows a financial reporting line instead of the personnel management line, thus allowing for a cost center to be assigned to two legal entities if different allocations need to be created by shared services. There is yet another hierarchy, which is called the location level hierarchy, according to which each individual branch, plant, or facility consolidates into a region and eventually into a business unit, although often a branch can be geographically located in one region while being organizationally assigned to a completely different region via a regional manager. Finally, there is a legal entity hierarchy kept by Corporate Finance that describes corporate regulations and taxes, and not reporting lines. As mentioned before, there is also the account hierarchy coming from Salesforce, which comes from how RevOps divides accounts during the closure process. Each node in the closure table is marked with its domain membership rather than assuming the same hierarchy for all domains. It is precisely the latter that does not work when a branch/cost center/account needs to have different rollups depending on its hierarchy type. The following example illustrates how role tier relates to scope. The scope of the executive role is restricted to the entire organization and multiple tiers down within the organization. This allows for visibility over thousands of entities. On the other hand, the regional vice president's role allows for a scope restriction to a particular region at the location level as well as the entire organizational hierarchy below the individuals within that region. Hence, this leads to a large scope but still within limits, and one which is not the entire organization but rather a subset thereof. Similarly, the cost center manager's role limits scope to the cost center he is in as well as any individuals reporting directly to him. In turn, the analyst's role has a scope that is limited only to certain cost centers that have been defined in advance and does not inherit scope from any of the two hierarchies. Troubleshooting / Lessons Learned The closure table going stale without anyone noticing This document explains the failure mode created at the beginning of the document: The API timeout made the closure table go out of date, and none of the components downstream had any way to check whether the data they were using was up to date before they started using it. To solve this problem, a mechanism was built to prevent synchronization after the closure table has aged past a certain point to alert the operation staff that outdated entitlements would not be served without notifying anyone first. Any aging at all in the closure table may create a picture of an organization that does not exist anymore due to a quick reorganization. Orphaned nodes from in-flight Salesforce account merges Merges may create the problem whereby the child is tied to an expired parent ID during the final propagation. During a normal run, this type of problem could involve from a few to several tens of entries, based on the level of mergers made prior to the cycle run. This type of problem creates the risk of attribution of wrong parents; thus, all the records involving this problem are isolated in a reviewing table. The full-rebuild job locking the access table during business hours In the first approach, the whole access table would be recomputed each time, which is very effective but leaves the USER_ENTITY_ACCESS locked for long enough to queue the BI refreshes after it. Since in the second approach recomputation occurs only in the subtree concerned—as shown by the cost center example—locks become almost negligible, even in cases of thousands of changes due to reorganization. Adaptive Planning throttling the security group push Individually pushing updates to the security groups became unsustainable due to the large number involved. This is as previously discussed, where a relatively small-sized batch was successful while significantly larger ones experienced errors of being throttled by the Integration API. Conclusion This article will describe the design of the dynamic multi-hierarchy security model that is based on Workday HCM, Salesforce, Snowflake, Power BI, Tableau, and Workday Adaptive Planning. This methodology allows one to reconcile the hierarchical data coming from four different sources, such as the organization hierarchy, cost center hierarchy, location hierarchy, and legal entity hierarchy, in addition to the account hierarchy coming from Salesforce, into a tagged graph rather than trees, which can be inconsistent. Moreover, visibility recomputation is achieved due to any actual change within these hierarchies and not depending on periodic reviews as in most cases. It will also be proven that having a single permission table per Snowflake row rather than permission tables for each BI tool helps reduce the visibility gap, which caused the initiation of this project.

By Yadi Reddy Mangannagari
Securing Database Connections With Mutual TLS
Securing Database Connections With Mutual TLS

Stolen credentials served as the entry point in 22% of breaches last year, and in attacks on basic web applications that figure climbs to 88%. Those numbers describe a password problem, and databases sit at the end of nearly every attack path. A username and password prove nothing about the machine presenting them. Mutual TLS closes that gap by requiring both sides of a connection to present certificates and prove who they are before a single query runs. Securing production database connections has convinced me that enterprises implement mutual TLS using readily available tools and established certificate management practices. What Certificate-Based Authentication Actually Closes Off A password travels, gets shared, gets phished, and gets left behind in a script. A certificate bound to a specific client does none of those things easily, which is why mutual authentication blunts three familiar attack patterns: stolen credentials replayed from an unfamiliar host, spoofed clients impersonating an application server, and lateral movement after an attacker gains an initial foothold. Enterprises usually maintain password rotation policies that are triggered after a set period or upon an employee's exit from the team. Certificate-based authentication safeguards the data in case a team misses rotating those credentials, because the password alone no longer grants entry. I ran into this while setting up an open-source alerting tool, where the password had to live in a config file or a session variable. The session variable fails when the tool auto-restarts during maintenance, leaving a hardcoded password or a decryption utility to mask it. A certificate addresses that problem because its lifetime can be governed by the organization's security policies. Once the certificate expires, a password alone is no longer sufficient to authenticate the client. Machine identities now outnumber human identities by more than 80 to 1 in the average organization, and each database connection string is one of them. The Rollout Decisions That Matter Most An mTLS program stands on three design choices. The first is the certificate authority, where an internal CA gives the team full control over issuance and revocation for database traffic that never leaves the estate. The second is cipher selection, which deserves more attention than it gets, because Oracle, MySQL, and MongoDB each negotiate TLS differently, and a cipher suite that works on one engine can fail the handshake on another. The third is rotation, and this is where programs die quietly. 81% of organizations have suffered at least two outages caused by expired certificates in a two-year window. Six months to a year is an ideal certificate lifetime across a polyglot estate, though the organization's security baselines govern, and estates handling critical PII or PCI data, or carrying past breach attempts, can justify a reduced lifetime. Renewing very frequently creates its own outages, because some systems require a reboot to bring new certificates into effect, a real challenge for heavily used 24/7 applications without a high availability solution ready. Keeping lifetimes consistent across Oracle, MongoDB, MySQL, and PostgreSQL helps manage the rotations, but databases are not all created in a single day, so expiry timelines differ and an inventory or dashboard tracking every expiry becomes essential. Above all, automating renewal wherever possible reduces the risk of downtime from an expired certificate. Securing the Monitoring Layer Itself Monitoring an encrypted estate raises a question teams often skip, which is how to keep the monitoring path from becoming the weak point. Many organizations use Prometheus to collect database metrics, with more than two-thirds of organizations running it in production, yet exporters may be deployed with unencrypted scrape endpoints if they are not configured to use TLS. One common deployment approach is to run the database exporter process on the database server itself. In Prometheus-based environments, configuring both the client connection (--config.my-cnf) and the exporter (--web.config.file) to use client certificates allows the monitoring pipeline to follow the same mutual authentication model as the database it monitors. This helps ensure that metrics are collected over authenticated, encrypted connections rather than introducing a weaker path into the environment. Choosing the Right Approach Organizations can implement mutual TLS using either commercial certificate management platforms or open-source tooling. The right approach depends on factors such as certificate volume, compliance requirements, auditing needs, and the operational resources available to manage the environment. Early in my career, I assumed enterprise-licensed products were the default choice for every deployment. Over time, I found that decision is more nuanced. Large environments managing tens of thousands of certificates may benefit from centralized lifecycle management, auditing, and governance features, while many organizations can successfully implement mTLS using open-source tools that meet their operational requirements. The priority should be selecting an approach that supports reliable certificate issuance, rotation, and revocation while integrating with existing security processes. Regardless of the tooling, a well-managed certificate lifecycle is what ultimately strengthens database authentication and reduces operational risk.

By Rahul Roy
Secure AI Systems: Defending Enterprise Applications Against Agent-Era Threats
Secure AI Systems: Defending Enterprise Applications Against Agent-Era Threats

The rise of autonomous AI agents within business software demands a fresh approach to security. Unlike earlier chatbot tools, modern agents act with real privileges, such as updating databases, calling microservices, composing and even executing code, or triggering workflows on their own. This shift expands the blast radius of any flaw or compromise. As one Microsoft analysis observes, today’s AI agents “can update database records, trigger enterprise workflows, access sensitive data, and interact with production systems all autonomously.” In practice, that means a mistake or exploit can have immediate operational impact instead of just a reputational cost. With agents in the loop, input manipulation becomes especially dangerous. Prompt-injection attacks let adversaries commandeer an AI by feeding it malicious instructions in user inputs or hidden in external data. A carefully crafted prompt or document can cause an agent to reveal secrets or perform harmful actions. These manipulations can be direct (an attacker’s text overriding the agent’s instructions) or indirect (for example, hidden commands embedded in HTML or metadata that the agent ingests). By definition, even inputs imperceptible to humans can subvert the model, forcing it to break safety rules. In effect, prompt injection can trick an AI into disclosing internal prompts, executing arbitrary commands, or making unauthorized changes. Protect AI Agents With Layered Security Controls Defending against prompt injection requires layered controls. It is not enough to trust the LLM’s built-in safeguards. The application must sanitize and constrain every input. For example, the OWASP GenAI guidelines recommend semantic filtering of user inputs and strict output validation. In practice, this often means cleaning or escaping suspicious tokens in the prompt, enforcing clear response schemas, and even tagging or quarantining untrusted data before it reaches the model. Developers should also build resilience into AI calls, for example by wrapping each agent invocation in a circuit-breaker or retry mechanism so that anomalous behavior triggers a safe fallback rather than a cascade of errors. Java @CircuitBreaker(name="agentService", fallbackMethod="fallbackAgent") @Retry(name="agentService", maxAttempts=3, backoff=@Backoff(delay=200)) public String executeAgentTask(String taskId, String input) { String safeInput = inputFilter.sanitize(input); return agentClient.postForObject("/tasks/" + taskId, safeInput, String.class); } private String fallbackAgent(String taskId, String input, Exception ex) { log.error("Agent {} failed: {}", taskId, ex.getMessage()); return "error"; } In this example, inputFilter.sanitize strips any suspicious content from the prompt, and the circuit-breaker ensures repeated failures lead to a controlled fallback. The fallbackAgent method logs the failure and returns a safe default response, preventing a hijacked prompt from causing uncontrolled retries or side effects. Embedding such patterns helps contain injected instructions and makes anomalies visible for audit. Agents also expand supply-chain and data-poisoning attack surfaces. AI applications often depend on third-party models, libraries, or datasets, each of which could harbor backdoors. In a real incident, attackers compromised an open-source Python package used in a model’s pipeline, effectively inserting malicious logic into every system that imported it. To guard against this, organizations must treat AI dependencies as critically as any library or service. Models and data should come from verifiable, signed sources, and teams should maintain an AI-focused Software Bill of Materials (SBOM) tracking each model and dataset. Regular scans of model files and packages (for example, by verifying cryptographic hashes or digital signatures) can detect tampering before models reach production. Another insidious vector is agent memory poisoning. Unlike stateless microservices, AI agents may accumulate knowledge across sessions or tasks. If an adversary can insert malicious “memories” or biased information into that knowledge base, the agent may repeat or amplify harmful logic over time. Researchers have shown that injecting only a few hundred carefully crafted documents into a training or retrieval database can reliably hijack a model’s outputs in specific domains. In an enterprise, this might translate to a support chatbot that starts rejecting valid requests or approving fraudulent transactions because its knowledge was skewed. Mitigations include thoroughly vetting any external data fed to the agent, cross-checking facts against trusted sources, and periodically resetting or auditing the agent’s internal state. For example, a system could clear an agent’s “short-term memory” after each sensitive transaction, or require digital signatures on any new knowledge items. Protect AI Agents With Layered Security Controls Identity and access control for agents is equally critical. Agents act as non-human service identities, so if an attacker steals an agent’s credentials, they essentially hijack its privileges. Recorded Future warns that “compromised credentials, SSO platforms, or agent identities could enable large-scale... data exfiltration”. In practice, a stolen token could let an attacker quietly siphon data or trigger commands anywhere the agent has access. To counter this, enterprises should issue each agent a unique short-lived token and restrict its scope strictly. Java String token = credentialService.issueShortLivedToken(agentId); apiClient.setAuthToken(token); apiClient.callExternalService(requestPayload); Here, each API call by the agent uses a fresh, scoped token. If the token is leaked or abused, its very short life and limited permissions contain the damage. In practice, agent tokens should be rotated frequently, and every action should be logged under the agent’s identity. If an agent suddenly tries to access an unexpected endpoint, automated policies should block or flag the request. In essence, treat agents like privileged users with their own IAM lifecycle by implementing least-privilege roles, multi-factor approvals for high-value operations, and full auditing of their activities. Multi-agent workflows introduce additional complexity. Agents often invoke other tools or orchestrate chains of sub-agents. In such pipelines, a compromise anywhere can cascade. For example, if Agent A trusts a data input or command from Agent B, and B has been misled or maliciously tampered with, A may unknowingly act on bad instructions. To mitigate this, every handoff between agents or tools should be authenticated and checked. Enforce endpoint authentication and message signing on each channel between agents, and apply authorization checks at every step. Segmentation and strong encryption on inter-agent communications can prevent a breach in one component from jumping to others. Monitor AI Agents for Anomalies and Unauthorized Actions At runtime, anomaly detection and monitoring provide a final safety net. Agents in production should exhibit well-defined baselines of behavior. An agent that usually looks up customer records, for instance, should not suddenly be streaming large volumes of payroll data. Security telemetry that logs every prompt, response, and tool invocation lets defenders spot when an agent deviates from its norm. Modern SIEM and AIOps platforms can ingest these logs and flag unusual patterns (for example, spikes in outbound data or unexpected API calls). By correlating agent activity with traditional logs and threat intelligence, teams can detect and contain a misbehaving agent before it causes systemic damage. In summary, securing enterprise applications in the agent era means integrating AI-specific defenses throughout the stack. Zero-Trust principles apply fully where we treat each agent call as untrusted until verified, grant agents only minimal permissions, and require human approval for any high-impact decision. Defense-in-depth remains essential as it sanitizes every input, isolates AI subsystems from sensitive resources, and monitors all outputs continuously. Industry standards are beginning to catch up. For example, NIST’s new AI Risk Management Framework and the Cloud Security Alliance’s guidelines explicitly recommend continuous threat modeling, red teaming of AI, and traceability for data and models. Ultimately, the agentic AI era raises the security stakes from theory into daily practice. Organizations that build AI-aware threat modeling, least-privilege IAM, prompt filtering, and anomaly monitoring into their DevSecOps pipelines will be best equipped to embrace AI agents safely. By doing the hard work now by integrating model security into the software lifecycle, enterprises can unlock the productivity of agents while keeping adversaries at bay.

By Uthej Mopathi DZone Core CORE
Making User-Generated Sites Embeddable: X-Frame-Options vs CSP Frame-Ancestors
Making User-Generated Sites Embeddable: X-Frame-Options vs CSP Frame-Ancestors

If you let users publish something, such as a page, prototype, or dashboard, sooner or later you want an "embed this" button so they can drop it into a blog, a portfolio, or docs, the way a CodePen result embeds. Then you ship the iframe, and it renders a blank box: refused to connect. The reflex is to blame the iframe. It's almost never the iframe. It's a response header. The Two Headers That Decide Whether You Can Be Framed There are two mechanisms, and they are not equivalent: X-Frame-Options is the legacy control. It has three meaningful states: DENY, SAMEORIGIN, and the deprecated, widely-ignored ALLOW-FROM. Crucially, there is no value that means "allow any origin" or "allow this list of origins." It is deny / same-origin / nothing-useful. If your edge returns X-Frame-Options: SAMEORIGIN, a third-party site can never frame you, full stop.CSP frame-ancestors is the modern replacement. It is part of Content-Security-Policy and takes a real source list: frame-ancestors 'none', 'self', https://example.com, or *. It is granular where X-Frame-Options is binary. The catch that trips people up: if you send both, X-Frame-Options is still honored by many browsers and will block framing regardless of how permissive your frame-ancestors is. So to actually be embeddable by third parties, you have to remove X-Frame-Options, not just add a permissive frame-ancestors next to it. The Footgun: One Global Security-Headers Middleware Here is the trap. The application that rendered our published sites already made the right call in code: it disabled frameguard and emitted a permissive frame-ancestors. And yet every embed was blank. The header was not coming from the app. It was re-added at the edge. A single shared "secure-headers" middleware, the kind every reverse proxy ships and every security checklist tells you to apply globally - included X-Frame-Options: SAMEORIGIN in its response headers. The proxy ran that middleware on the router that served published user sites, stamping SAMEORIGIN on top of the app's deliberate "please frame me" headers. The edge won. State it plainly: applying one blanket security-headers policy to every route is a footgun the moment one of those routes is supposed to serve embeddable content. That middleware is correct for your API and your authenticated app. It is wrong for the one route whose entire job is to be put inside someone else's <iframe>. The Fix: Scope Headers Per Trust Zone The fix is not "turn off security headers." It is to stop treating every route as one trust zone: Authenticated and sensitive routes (/api, realtime/WebSocket, the editor app) keep the full secure-headers set, including X-Frame-Options: SAMEORIGIN. Those should never be framed; clickjacking protection stays.The route that serves published, public, client-only user pages gets a near-identical header set - same X-Content-Type-Options, Referrer-Policy, Strict-Transport-Security - but without X-Frame-Options. Whether such a page can be framed is then governed by the frame-ancestors the page itself serves. In practice, that is a second middleware that is a copy of the first minus one header, pointed only at the published-pages router. Surgical. Nothing else loses protection. YAML secure-headers: # sensitive routes - keeps clickjacking protection headers: customResponseHeaders: X-Frame-Options: "SAMEORIGIN" contentTypeNosniff: true referrerPolicy: "strict-origin-when-cross-origin" stsSeconds: 31536000 pages-headers: # same set, minus X-Frame-Options - embeddable pages only headers: contentTypeNosniff: true referrerPolicy: "strict-origin-when-cross-origin" stsSeconds: 31536000 Then the page that is meant to be embeddable expresses its own policy: YAML Content-Security-Policy: frame-ancestors *; (or a specific allowlist, if only certain hosts should embed it). Embedding User-Generated Content Safely "Make it embeddable" and "make it safe" have to hold at the same time, because you are putting code you did not write into a frame. A few rules that travel well: Isolate every project on its own origin. Serve each published site from its own subdomain ({slug}.example.io), never a shared path. Origin isolation means one project's script cannot reach another's storage, cookies, or DOM. This is the single biggest lever.Sandbox the frame. The embedding side should use <iframe sandbox="allow-scripts allow-popups ..."> and grant only the capabilities the content needs. Omit allow-same-origin where you can, so the framed document runs with an opaque origin.Let the page opt out. A published page should be able to override the edge default and refuse framing - its own X-Frame-Options / frame-ancestors should win over the proxy default. Author intent beats infrastructure default.Keep authenticated surfaces un-framable. The embeddable posture applies to public content only. Anything behind a login keeps SAMEORIGIN. This is the posture we landed on at Playcode, an AI website and app builder: published projects each live on their own origin, the published-pages route drops X-Frame-Options so a one-line embed drops a live project into any blog or docs page, while the editor, API, and Playcode Cloud backend keep full clickjacking protection. A static published page carries the same minimal framing risk that previews and custom domains already had. The difference is that it is now a deliberate, scoped decision instead of an inconsistent accident across routes. Takeaways A blank "refused to connect" embed is almost always X-Frame-Options, not your iframe.X-Frame-Options cannot express "allow these origins" - use CSP frame-ancestors for anything granular, and drop X-Frame-Options entirely on routes that must be embeddable.Do not apply one global security-headers middleware to routes that serve embeddable content; scope headers per trust zone.Embeddability and safety coexist through origin isolation, the iframe sandbox attribute, and letting the page author's policy win over the edge default.

By Ruslan Ianberdin
Why Your Terraform Drift Alerts Are Useless (And How to Fix Them)
Why Your Terraform Drift Alerts Are Useless (And How to Fix Them)

Let me describe a workflow that exists in thousands of engineering organizations right now. Somebody sets up a cron job. It runs terraform plan against production every few hours. When the plan output isn't empty, it fires a Slack notification. The team calls this "drift detection." For about two weeks, it works. Engineers look at every alert, investigate changes, and fix things. Then the noise starts. Auto-scaling groups change desired_capacity. It's not drift; that's the system doing its job. Someone updated a tag through the cost allocation tool. An external script modified a description field. The load balancer's idle timeout was changed by an automation nobody remembers writing. Within a month, the Slack channel is muted. Within two months, the cron job is either disabled or silently ignored. And that's when someone modifies a security group through the AWS console "temporarily" and forgets to revert it. I've seen this pattern at every organization I've worked at. The problem isn't that drift detection doesn't work. It works well. It finds everything, tells you nothing about what matters and what is actually important, and eventually drowns in its own noise. The Signal-to-Noise Problem Here's the main issue with terraform plan as a drift detection mechanism. Something changed, or it didn't. There's no concept of severity, no notion of risk, no way to distinguish between a tag modification and an exposed database. We cannot tell from the change how much of a risk that is. Consider two drift events: Event A: aws_s3_bucket.logs the tags.Environment attribute changed from "production" to "prod"Event B:aws_security_group.api_gateway — the inbound rule now includes a rule allowing port 22 from 0.0.0.0/0 Terraform plan presents both as equivalent changes. But Event A is a cosmetic inconsistency that has zero operational impact. Event B is an active security vulnerability that could be the first step in a breach. When you're getting 40 alerts a day and most of them look like Event A, how long does it take before you stop carefully examining each one? Studies on alert fatigue show that when engineers are flooded with too many alerts, it becomes harder to respond effectively. As a result, critical issues can be overlooked along with less important alerts. Monitoring tools addressed this problem years ago by prioritizing alerts based on severity and sending them to the right teams. Infrastructure drift detection has not yet adopted these practices. Thinking in Severity Tiers The solution isn't to stop detecting drift. It's to classify it. When I started building a drift detection tool for my own use, severity classification was the feature I cared about most. After iterating on several models, I landed on four tiers: Critical: Changes that directly affect security boundaries. If someone modified a security group's ingress rules, an IAM policy, a KMS key policy, or an S3 public access configuration, I want to know about it right now. High: Changes that affect compute capacity, data persistence, or encryption. An instance type change in production means your capacity planning is wrong. A database with publicly_accessible flipped to true is a problem waiting to happen. An encryption setting change needs investigation.Medium: the default bucket for attribute changes that don't match any explicit rule. Worth knowing about, not worth getting paged for.Low: Tags, descriptions, labels. The metadata that external systems modify constantly and that nobody needs to be alerted about. At first, I tried using three severity levels. However, that was too simple because it did not clearly separate different types of serious issues. For example, changing an IAM policy could create a security risk, while changing an instance type could cause performance or capacity problems. Both are important, but they have different impacts. I also tried using five severity levels, but that was too detailed. It became difficult to consistently decide which level an issue belonged to, especially when the differences between levels were small. Attribute-Level Classification The key insight is that severity depends on which attribute changed, not just which resource type changed. An aws_security_group resource changing its tags is low severity. The same resource changing its ingress rules is critical. Classifying by resource type alone would make all security group changes critical, which defeats the purpose. You'd still get noise from tag modifications. The classification engine I built uses pattern matching rules that match against the resource type and attribute combination. For example: aws_security_group..ingress maps to critical, aws_security_group..tags maps to low, aws_iam_policy..policy maps to critical, aws_instance..instance_type maps to high, and any *.tags pattern maps to low. When a resource has multiple changed attributes at different severity levels, the maximum applies. A security group with both a tag change (low) and an ingress change (critical) gets reported as critical. This prevents the scenario where someone dismisses a critical alert because it's attached to what looks like a mostly-harmless tag update. I chose fnmatch glob patterns over regular expressions deliberately. The people editing these rules are operations engineers responding to incidents at 2 AM, not writing parsers. A pattern like aws_security_group.*.ingress is instantly readable. The Numbers I tested this approach across 150+ Terraform workspaces managing 847 AWS resources. I introduced 62 drift events across four categories: security-relevant changes (security group and IAM modifications), operational changes (instance types, database configs), metadata changes (tags, descriptions), and expected changes (auto-scaling adjustments). With binary detection (standard terraform plan), all 62 drift events were flagged as 100% of changes, with security-relevant ones buried in noise. Filtering to High and Critical severity only reduced the alert count to 17 (27% of total) while still catching 7 of 8 security-relevant changes 94% security coverage. Adding ignore rules for expected drift like autoscaling reduced it further to just 12 alerts (19% of total) at the same 94% security coverage. That's a 73% reduction in alert volume while retaining 94% of security-relevant changes. The severity classification also performed well against manual expert review. Two engineers independently labeled all 62 events. Agreement rates with automated classification: critical 96%, high 91%, medium 88%, low 95%. The Ignore Layer Beyond severity classification, there's a category of drift that shouldn't be classified at all it should be filtered out entirely. Auto-scaling groups change desired_capacity every few minutes. That's not drift. That's the autoscaler doing exactly what it's supposed to do. ECS services change desired_count for the same reason. Tag attributes like LastModified get updated by external tools constantly. An ignore file (similar in concept to .gitignore) handles this. You list patterns like aws_autoscaling_group..desired_capacity and aws_ecs_service..desired_count, and those changes are filtered out before classification, removing an entire class of noise without any risk to security coverage. Configuration as Institutional Knowledge Here's something I didn't anticipate when I started building this: the severity configuration file becomes a living document of your organization's security values. When you mark a rule like aws_rds_instance.*.storage_encrypted as critical, you are defining what is important for your environment. When you add a new pattern after an incident, you are documenting a lesson learned. Over time, this knowledge is stored in a version-controlled YAML file instead of relying on team members to remember it. So when a new engineer asks, "Do we care about CloudFront origin changes?", they can find the answer directly in the configuration. That incident comment in the config file is institutional knowledge being captured and enforced, not just documented. Cross-Cloud Applicability The pattern-based approach works across cloud providers. For Azure, patterns like azurerm_network_security_group..security_rule, azurerm_role_assignment. and azurerm_key_vault_access_policy.* map to critical, while azurerm_virtual_machine.*.vm_size maps to high. For GCP, patterns like google_compute_firewall..allow, google_compute_firewall..source_ranges, and google_project_iam_binding.* map to critical, while google_compute_instance.*.machine_type maps to high. The severity tiers are universal. The patterns are provider-specific. A well-maintained rule set should cover the top 20-30 most security-sensitive resource types and attributes for each cloud provider you use. From Detection to Governance Severity classification opens the door to something more powerful than alerting: governance. Once drift has a severity score, you can build policies around it. In CI/CD, you can fail the deployment pipeline if Critical drift exists in the target environment. For escalation routing, you can send critical drift to PagerDuty, high to Slack, and log medium/low silently for weekly review. For auto-remediation, you can automatically run terraform apply for low-severity drift like tag corrections but require human approval for anything high or above. For compliance, you can generate weekly reports showing drift by severity for security review. Getting Started If you want to try this approach, tfdrift is the open-source tool I built implementing everything described in this article. Install it with pip install tfdrift, then run tfdrift scan --path ./your-terraform-dir to scan your infrastructure. Run tfdrift init to generate a starter configuration file. It ships with 60+ built-in severity rules for AWS, Azure, and GCP, all configurable via YAML. It supports Slack and PagerDuty notifications, JSON/Markdown/HTML output, auto-remediation with safety guards, and OpenTofu via a --binary flag. But the specific tool matters less than the approach. The core idea — classifying drift by security impact and routing alerts accordingly — is implementable with any combination of terraform plan, a JSON parser, and a pattern matcher. Key Takeaways Binary drift detection creates alert fatigue. When all changes are treated equally, teams stop checking, and that's when security-critical changes get missed. Four severity tiers hit the right granularity. Critical for security boundaries, high for compute and encryption, medium for other changes, and low for metadata. Three is too coarse, five is too hard to distinguish consistently. Classify by attribute, not just resource type. A security group changing tags is low, but the same resource changing ingress rules is critical. Attribute-level classification is what makes severity useful. Severity filtering reduces alert volume by 73% while maintaining 94% security coverage based on evaluation across 150+ Terraform workspaces. The severity config becomes institutional knowledge. Your configuration file is a version-controlled, reviewable record of what your organization considers security-critical infrastructure changes.

By Sudarshan Bhagvan Thakur
Multi-Account AWS Architecture: Isolating PHI Workloads Without Slowing Down Engineering Teams
Multi-Account AWS Architecture: Isolating PHI Workloads Without Slowing Down Engineering Teams

Most engineering teams working on healthtech applications reach a point where someone asks a question that sounds simple but isn't: How do we make sure a developer testing a new feature can't accidentally access production patient data? The answer determines whether the architecture that follows will be auditable or not. Teams that answer it with process — "we have policies about that" — spend the next 18 months patching access-control gaps that reopen every time a new engineer joins or a new service gets wired in. Teams that answer it architecturally spend a week setting up AWS Organizations correctly and then largely stop thinking about it. This article covers the multi-account architecture pattern for HIPAA-compliant infrastructure — specifically, the account structure decisions that either enforce PHI workload isolation or make it a permanent source of audit findings. Why Single-Account PHI Isolation Fails at the Seams A single AWS account running production, staging, and development workloads creates a specific problem that IAM policies alone cannot fully solve. The issue is not that IAM is insufficient as a technology. IAM policies enforced within an account are only as reliable as the discipline of the people who manage them. A policy that restricts a developer's access to production RDS today can be modified tomorrow by anyone with sufficient IAM permissions. Nothing in the account structure itself prevents the boundary from being crossed. In practice, the gaps show up in predictable ways. A pipeline service role gets broad permissions during a sprint because scoping them properly would have taken an extra hour. An engineer copies an IAM role from staging to production because it was faster than creating a new one. A debugging session in production happens under an account that was supposed to be read-only. None of these are malicious decisions. They are the natural result of putting access control boundaries inside an environment where the people who need to cross them also have the permissions to do so. The access control problem that surfaces during security reviews is almost always this one — not a missing encryption setting or an unpatched vulnerability, but access boundaries that exist on paper and drift in practice. The Multi-Account Model: Enforcement at the Boundary AWS Organizations with a properly structured multi-account hierarchy solves this problem by moving the enforcement point outside the accounts being protected. The boundary is no longer an IAM policy that someone with IAM permissions can modify. It is an account boundary that the engineers inside those accounts cannot cross, enforced by Service Control Policies applied at the organizational unit level. The recommended structure has four organizational units under the root: a Security OU containing a Log Archive account and a Security Tooling account, a Production OU containing only the Production account where PHI workloads run, a Non-Production OU containing Staging and Development accounts, and a Shared Services OU containing the account used for CI/CD pipelines, DNS, and shared tooling. The Production OU sits under its own organizational unit with SCPs that restrict what can happen inside it, regardless of what IAM policies exist within the production account itself. An engineer whose IAM role in the development account grants broad permissions has those permissions scoped to the development account. Crossing into production requires a separate role, in a separate account, with a separate set of credentials. The architectural boundary is the enforcement mechanism, not the IAM policy. The Log Archive account under the Security OU serves a specific purpose: it is the only account to which CloudTrail logs from all other accounts are delivered, and it is an account to which production engineers have no write access. This means the evidence trail for PHI access events cannot be modified by the accounts generating those events - which is exactly what auditors verify when they ask about log integrity. Service Control Policies: What to Enforce at the OU Level SCPs applied to the Production OU are where the architectural enforcement becomes concrete. The first policy prevents anyone inside the production account from disabling CloudTrail, including account administrators: JSON { "Effect": "Deny", "Action": [ "cloudtrail:StopLogging", "cloudtrail:DeleteTrail", "cloudtrail:UpdateTrail" ], "Resource": "*" } CloudTrail continuity across the full audit period is not something that should depend on engineering discipline. It should be architecturally enforced. An account that can leave the organization can escape every SCP applied to it. This policy closes that path: JSON { "Effect": "Deny", "Action": "organizations:LeaveOrganization", "Resource": "*" } PHI that moves outside defined regions may fall outside data residency commitments. This policy locks the production account to specific regions: JSON { "Effect": "Deny", "Action": "*", "Resource": "*", "Condition": { "StringNotEquals": { "aws:RequestedRegion": ["us-east-1", "eu-west-1"] } }, "NotAction": [ "iam:*", "organizations:*", "route53:*", "budgets:*", "waf:*", "cloudfront:*", "globalaccelerator:*", "importexport:*", "support:*", "trustedadvisor:*" ] } EBS encryption is not enforced by default in all account configurations. This policy makes an unencrypted volume impossible to create in the production account: JSON { "Effect": "Deny", "Action": "ec2:RunInstances", "Resource": "arn:aws:ec2:*:*:volume/*", "Condition": { "Bool": { "ec2:Encrypted": "false" } } } Cross-Account Access: The Pattern That Doesn't Create New Gaps Multi-account architecture introduces a problem engineers feel immediately: how does anything talk to anything else? A CI/CD pipeline in the Shared Services account needs to deploy to production. A developer needs read access to production logs during an incident. A monitoring service needs metrics from all accounts. The answer is cross-account IAM roles with tightly scoped trust policies. A role created in the production account with minimum required permissions defines a trust policy that allows only specific principals from specific accounts to assume it, and only under specific conditions like MFA or an external ID: JSON { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::SHARED-SERVICES-ACCOUNT-ID:role/DeploymentRole" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "deployment-pipeline-prod" } } } ] } The deployment role in the Shared Services account can assume the deployment role in production - but only that role, only from that account, and only with the correct external ID. A developer's personal IAM credentials cannot assume it. An engineer who compromises the development account cannot use that foothold to pivot into production. This pattern creates cross-account access without creating a backdoor through the account boundary. The boundary holds because the trust relationship is explicit, narrow, and auditable through CloudTrail - every role assumption generates a log entry in both accounts. What This Architecture Makes Provable The operational argument for multi-account PHI isolation often focuses on security. The architectural argument that matters more for engineering teams dealing with audits and enterprise security reviews is about provability. In a single-account setup, proving that a developer did not touch production PHI during a given period requires auditing IAM policies, CloudTrail logs, and access history, and then arguing that the policies were correctly configured and consistently enforced throughout the period. There is always a gap between what the policy said and what actually happened, and that gap is what auditors probe. In a multi-account setup, the same question has a simpler answer. The developer's credentials are scoped to the development account. The development account has no access to the production account's resources. Access to production PHI requires a separate role assumption that is logged, requires separate credentials, and would appear immediately in CloudTrail. You are not arguing that the configuration was correct. You are pointing to an architectural boundary that makes the question moot. This shift from arguable to verifiable is what separates teams that sail through security reviews from teams that spend three weeks responding to follow-up questions. The Operational Overhead Is Smaller Than It Looks The most common objection to multi-account architecture from engineering teams is overhead. More accounts means more IAM configuration, more billing to reconcile, more consoles to log into. In practice, this friction is front-loaded and largely disappears once the structure is in place. AWS Control Tower reduces the account provisioning overhead significantly - new accounts inherit the correct SCP structure, logging configuration, and security baseline automatically. Account Vending Machine patterns built on top of Service Catalog or Terraform can provision a correctly configured new account in minutes. After the initial setup, adding a new account is not significantly more work than adding a new VPC. The billing concern is resolved through AWS Organizations consolidated billing, where all accounts roll up to a single payment method with unified cost visibility. The console switching concern is resolved through IAM Identity Center, which provides a single sign-on entry point across all accounts in the organization. The overhead that remains is real but small. The alternative - treating IAM policies inside a single account as the primary PHI protection mechanism - creates ongoing operational overhead that grows with the team and never fully goes away. Final Thoughts PHI workload isolation is an architectural problem, not a policy problem. IAM policies enforced inside an account are only as reliable as the operational discipline of the team maintaining them. Account boundaries enforced by SCPs at the organizational level are reliable by construction — they hold regardless of what happens inside the accounts they protect. The multi-account structure described here is not a compliance checkbox. It is the architecture that makes the access control claims in a security review actually true rather than approximately true with caveats. When an auditor asks how you prevent developer access to production PHI, the strongest answer available on AWS is an account boundary that the developer's credentials cannot cross. Building that boundary is a week of work. Not building it is a permanent source of audit findings.

By Garik H
How to Secure Fintech REST APIs Against BOLA Vulnerabilities
How to Secure Fintech REST APIs Against BOLA Vulnerabilities

Broken Object Level Authorization (BOLA) occurs when a REST API exposes an object identifier—such as an account, transaction, or loan ID — without verifying whether the authenticated user is authorized to access that specific resource. To protect fintech REST APIs, implement server-side authorization checks for every object request, validate permissions using the user's authenticated context and resource ownership, and avoid relying on client-supplied IDs alone. Using unpredictable identifiers such as UUID v4 or ULIDs can reduce object enumeration, but they should be treated as an additional security layer—not a replacement for authorization. Expert Insight: In retail banking systems, BOLA can expose sensitive customer and financial data when attackers manipulate object IDs, such as changing /api/v1/accounts/1001 to /api/v1/accounts/1002. Randomized identifiers make enumeration harder, but the core defense is object-level authorization on every API request. Policy-based controls, including tools such as Open Policy Agent (OPA), can help enforce consistent ownership and access rules across services. 1. Understanding BOLA in Fintech Ecosystems Fintech APIs handle highly sensitive operations, including transaction retrieval, account information, payment processing, and ledger-related activities. Broken object-level authorization (BOLA) occurs when an API uses a client-supplied object identifier to retrieve or modify a database record without verifying that the authenticated user has permission to access that specific resource. Authentication vs. authorization: Authentication confirms who the user is, such as validating a JWT. Authorization determines what that authenticated user is permitted to access or modify. A valid login does not automatically grant access to every financial object.The scale of risk: Modern open-banking ecosystems connect banks, fintech platforms, payment providers, and third-party applications. A missing object-level authorization check can therefore expose sensitive account, transaction, or payment data beyond the intended user or organization. Example: If a customer can access /api/v1/accounts/1001 and simply change the ID to /api/v1/accounts/1002 to retrieve another customer's account, the endpoint has a potential BOLA vulnerability. 2. The Anatomy of a Banking BOLA Attack Consider a poorly secured REST API endpoint used to fetch a customer's monthly credit card statement: HTTP GET /api/v1/statements?account_id=89234 An attacker first logs in with their own valid account and accesses their statement using account_id=89234. They then use an interception proxy such as Burp Suite to change the account ID in the outgoing HTTPS request: HTTP GET /api/v1/statements?account_id=89235 If the backend directly uses 89235 to query the database without checking whether this account belongs to the authenticated user, the API may return the victim's private banking information. This is a classic BOLA vulnerability. The main issue is that the API checks whether the user is logged in, but fails to check whether that user is actually allowed to access the requested account. 3. Top 5 Architectural Practices to Mitigate BOLA a. Avoid Sequential Integer IDs in Public APIs Avoid exposing simple auto-increment database IDs such as 1, 2, or 3 through public API endpoints. Use unpredictable identifiers such as UUIDv4 or ULID (Universally Unique Lexicographically Sortable Identifier) instead. This makes automated ID guessing and enumeration much harder. However, random identifiers should be treated as an extra security layer, not as a replacement for proper authorization checks. b. Do Not Depend on Client-Supplied Parameters for Authorization The client should never decide the access boundary simply by sending an account or resource ID in the URL. Instead, the backend should get the authenticated user's identity from a securely verified session or validated JWT claims and then check whether that user has permission to access the requested resource. c. Use Fine-Grained Access Control (FGAC) Use authorization models such as attribute-based access control (ABAC) or relationship-based access control (ReBAC) when the application needs more detailed permission rules. For example, the system can maintain clear relationships between users, accounts, transactions, loans, and other financial resources. The API can then check whether the requested object is actually linked to the current user's permitted scope. d. Centralize Common API Security Policies In a microservices environment, repeating authorization logic separately in every service can create gaps and inconsistent rules. API gateways such as Kong, Apigee, or AWS API Gateway can help enforce common authentication, token validation, routing, and security policies at the edge. However, sensitive object-level authorization should still be enforced by the service that owns the resource. e. Shift Security Testing Left Include API authorization testing throughout the CI/CD pipeline instead of waiting until production. Automated security tests can change resource identifiers, use different user identities, and verify that unauthorized requests are rejected. For example, a test can confirm that User A cannot access User B's account and that the API returns an appropriate 403 Forbidden or 404 Not Found response according to the application's security design. Securing fintech APIs (such as AutoPay By NPCI) against BOLA is critical for safeguarding sensitive user data [OWASP]. Teams can utilize architecture resources and deployment calculators to audit system compliance costs, optimize processing infrastructure, and seamlessly bridge secure development workflows with enterprise-grade financial technology standards. 4. Implementing Contextual Code-Level Checks At the code level, a secure Java/Spring Boot controller should perform an object-level authorization check before passing the request to the service or repository layer. Java @GetMapping("/api/v1/accounts/{accountId}") public ResponseEntity<AccountDetails> getAccount(@PathVariable String accountId, @AuthenticationPrincipal JwtPrincipal principal) { // Check if the authenticated user UUID matches the requested resource ownership if (!authorizationService.isOwner(principal.getUserId(), accountId)) { throw new AccessDeniedException("Unauthorized resource access attempt."); } return ResponseEntity.ok(accountService.findById(accountId)); } 5. The Verdict: How to Audit Your System Step 1: Review all public REST API endpoints that accept user IDs, account IDs, transaction IDs, or other object identifiers through URL paths, query parameters, or JSON request bodies.Step 2: Make sure your QA and security tests include cross-user and cross-tenant access checks. For example, authenticate as User A and try to access User B's statement. The request should be rejected.Step 3: Use centralized authorization controls, middleware, or framework-level security components to apply identity and permission checks consistently across API endpoints. This helps reduce the chance of one controller accidentally missing an important authorization check. A proper BOLA audit should verify not only whether users are authenticated, but also whether they can access only the financial objects they are actually authorized to use.

By Nanne Parmar
Why DAST Findings Are Hard to Fix and How to Make Them Actionable
Why DAST Findings Are Hard to Fix and How to Make Them Actionable

Dynamic testing is essential because it uncovers vulnerabilities in running applications. But while SAST gets the attention because it’s shift-left and relatively straightforward to fix, DAST often gets stuck in the backlog. Application security testing generally splits into two approaches. SAST (static analysis) scans source code before it ever runs, catching issues while a developer is still in the file, which is why fixes tend to happen fast. You're editing code you just wrote, with full context on what it does and why. DAST (dynamic analysis) works differently. It tests an application while it's running, sending real requests at live endpoints to see what breaks, the same way an attacker would probe it from outside. That's what makes DAST so valuable. It catches vulnerabilities that only show up in production behavior, not in the code itself. But it's also what makes DAST findings harder to act on. A SAST finding points to a file and a line. A DAST finding might simply point to a URL that returned something it shouldn't have, with no direct link back to the code that caused it. That gap is why DAST findings so often stall in the backlog while SAST findings get resolved first. Let’s break down how developers can make DAST findings behave less like alerts and more like bug reports they can actually work on. A Finding Needs Repro Evidence, Not Just a Vulnerability Name A vulnerability name alone is not very useful. What matters are the details and the ability to quickly reproduce the issue. A developer needs the request, the payload, the vulnerable parameter, and the auth context it ran under. It’s important to hand the finding over as a request the developer can run, not just a description they have to read. A HAR file captures the full request and response cycle. A working curl command lets someone fire off the exact same request from their terminal and watch it fail the same way. Either format turns a mere alert into a bug report worth acting on. A Finding Doesn't Know Who Owns It A DAST finding lives at the network layer. It knows the endpoint that responded and the payload that broke it. On the other hand, DAST is unaware of which repository owns that endpoint, or which team gets paged if it breaks. It’s important to remember here that DAST works by hitting an application from the outside, in the same way that an attacker might, so it was never going to have visibility into the underlying codebase. If you can’t bridge that gap, then a finding just sits there, because nobody can confirm it's theirs to fix. The solution comes from correlating the runtime finding with a repository and a code path. Pairing DAST with SAST data helps, since SAST already has the codebase mapped, though the correlation is rarely perfect. A monorepo or a shared service layer can leave 'this endpoint belongs to this team' genuinely unclear, no matter how good the integration is. API inventory data helps too, tying live endpoints back to the services behind them, but it depends on that inventory being kept current, which not every team manages well. The finding still won’t be actionable on its own. But if you manage to turn "this URL is vulnerable" into "this line, in this repo, probably owned by this team," then you’ve effectively given a developer a starting point from which they can actually work. A Severity Score Doesn't Tell You If Anyone Can Reach It Knowing where a finding lives and who owns it still doesn't guarantee anyone acts on it. A developer with a backlog full of feature work needs a reason to bump a security fix ahead of everything else, and a severity label alone rarely makes that case. CVSS scores describe how bad a vulnerability might be theoretically, based on the vulnerability itself, but they do so without any context into the environment in which the vulnerability sits. A 9.8 score on an endpoint that isn't internet-facing and requires authentication hardly deserves the same attention as a 9.8 that's wide open. Indeed, the score alone can't tell a developer which type of situation they're facing. The catch is that this context isn't always so easy to attach. Someone has to actually know the app's architecture well enough to know what’s actually reachable — parameters that are often under-documented, especially as apps undergo so many dynamic changes. When reachability context is missing, the honest move is to flag the finding as an unverified exposure rather than allow an outdated assumption to drive a prioritization decision. Getting this right matters more than getting to it fast, since a developer who acts on bad exposure data once will start ignoring the context field altogether. Findings Die in Dashboards Developers Never Open A finding can have perfect reproduction steps, a clear owner, and full exploitability context, and still go nowhere if it's sitting in a security dashboard the developer never logs into. Not to imply that this is a discipline problem. Developers simply prefer to work out of their own backlog – Jira, Linear, whatever the team uses – and a separate security tool is one more login, one more context switch, one more thing to remember to check. Ideally, findings should land automatically in the same place developers already work, whether that's a ticket created through the platform's API or a message in the team's Slack channel. Prioritizing these pushes can be challenging to nail down as well, because auto-creating a ticket for every low-severity finding just adds noise to a backlog, effectively training developers to ignore the security label entirely. Opening a dev ticket works best when it's reserved for critical and high-severity findings. Lower-severity findings are often better left in a queue that gets triaged in batches. A Fix That's Never Retested Is Just a Guess The final step in remediation is confirming that the fix actually worked. Ironically, it's the step most likely to get skipped. All too often, a developer closes the ticket, moves on, and nobody circles back to check whether the underlying request still fails the same way it did before. The fastest way to check is to simply rerun the exact request that triggered the finding in the first place. This can take place either as a one-click retest button or as an automated check upon the next deploy. No matter which method you use, the developer shouldn't have to manually rebuild the original request from memory or dig back through the ticket to reconstruct what to test. Retesting is also where a false sense of progress might creep in. A finding that stops firing isn't necessarily a finding that's been fixed – the endpoint could have moved, a WAF rule could be masking it, the auth flow could have changed in a way that makes the original payload irrelevant without addressing the underlying flaw. A clean retest is useful as a signal, not proof, and treating it as automatic closure is how vulnerabilities quietly resurface months later under a different path. Conclusion DAST finds real vulnerabilities; they’re just harder to act on. But that doesn’t make it acceptable to ignore them. Reproduction evidence turns an alert into something the developer can run and test themselves. Mapping a finding to its repo turns it into an assigned task. Exploitability context gives it urgency. Routing it into Jira or Slack gets it seen. Retesting proves that the fix has taken hold. With a few tweaks in how findings are created and reach developers, critical vulnerabilities that surface during dynamic testing can finally get the attention they deserve.

By Philip Piletic DZone Core CORE

Monthly Top Security Experts

expert thumbnail

Apostolos Giannakidis

Product Security,
Microsoft

expert thumbnail

Jithu Paulose

Data/AI,
Cisco

expert thumbnail

Josephine Eskaline Joyce, Ph.D

Chief Architect,
IBM

Josephine Eskaline Joyce is an STSM and Chief Architect at IBM with more than 25 years of experience in designing and advancing enterprise cloud architectures, platform engineering solutions, and security-first cloud practices. Her expertise spans Infrastructure as Code, AI-driven automation, resilient DevOps, cloud security, and scalable cloud-native platforms. She is an IBM Master Inventor with patented innovations and has authored research articles on cloud-native systems, automation, artificial intelligence, and emerging technologies. The views expressed here are solely her own.
expert thumbnail

Igboanugo David Ugochukwu

Technical Writer,
Self-Employed

Igboanugo David Ugochukwu is a DevSecOps and cybersecurity writer whose work has appeared in The newstack.io, hashnode, EM360, InfoSecurity Buzz, and DZone and many more. He helps organizations navigate the risks and rewards of AI-augmented software development. Let's connect to explore custom integrated messaging and content solutions tailored to amplify your leadership vision. [email protected]

The Latest Security Topics

article thumbnail
Data Governance for the Agentic Era
This article explores how modern data governance and AI-ready architecture help enterprises manage data quality, risk, compliance, and AI at scale.
September 14, 2026
by Dr Gopala Krishna Behara DZone Core CORE
· 1,888 Views · 2 Likes
article thumbnail
A Firewall for AI Agents: Enforce Authority at Every Tool Call
The control that actually stops an AI agent from causing harm runs on its tool calls, not on the words going into the model.
September 14, 2026
by Jithu Paulose
· 2,029 Views
article thumbnail
Why Continuous Application Security Testing Is No Longer Optional
Point-in-time vulnerability testing is just a snapshot. In today’s threat environment, continuous application security testing shows real security posture.
September 11, 2026
by Jigar Shah
· 2,302 Views
article thumbnail
Part 2: Securing and Scaling Goose-to-Java Agent Traffic With agentgateway
Deploy agentgateway as a security proxy between Goose AI agents and Quarkus MCP servers to enforce JWT auth, RBAC, and tool-poisoning guardrails.
September 9, 2026
by Daniel Oh DZone Core CORE
· 3,119 Views · 1 Like
article thumbnail
Pipelines on Fire: Why Your CI/CD Tools Are the New Cyber Battlefield
Learn why CI/CD pipelines are becoming major security targets and how to protect runners, secrets, AI tools, and software supply chains.
September 9, 2026
by Igboanugo David Ugochukwu DZone Core CORE
· 2,787 Views · 1 Like
article thumbnail
Your Email Security Is a DNS Configuration Problem
Every email authentication control is really a DNS record, so most email security failures are DNS mechanics, not protocol misunderstandings.
September 8, 2026
by Vadim Ivanov
· 1,649 Views
article thumbnail
The Bottleneck of Scaling
Learn how modern languages help developers take care of behind-the-scenes file descriptor management, kernel memory management, and heap management.
September 3, 2026
by Vishal Bhatia
· 2,643 Views · 1 Like
article thumbnail
Gossip on Cryptography: Part 3
In Part 3 of this casual, jargon-free series, we break down hashing, salting, rainbow table attacks, and asymmetric encryption (RSA) — all with everyday analogies.
September 3, 2026
by Sahil Aggarwal
· 2,357 Views · 3 Likes
article thumbnail
Beyond Agent-Washing: The Engineering Principles Behind Production-Ready AI Agents
Enterprise AI agents need secure execution boundaries, deterministic logic, identity, governance, and auditing—not just intelligent models—to safely act in production.
September 2, 2026
by Igboanugo David Ugochukwu DZone Core CORE
· 2,719 Views
article thumbnail
Designing a Dynamic Multi-Hierarchy Security Model for Analytics and Decision Support Systems
How we built row-level security across Workday HCM, Salesforce, Snowflake, Power BI, and Adaptive Planning that survives a reorganization.
August 31, 2026
by Yadi Reddy Mangannagari
· 2,327 Views · 1 Like
article thumbnail
Securing Database Connections With Mutual TLS
Mutual TLS strengthens database security by authenticating both clients and servers, reducing the risks associated with password-based authentication.
August 31, 2026
by Rahul Roy
· 2,639 Views
article thumbnail
Secure AI Systems: Defending Enterprise Applications Against Agent-Era Threats
Secure enterprise AI agents with zero-trust, prompt defenses, identity isolation, secure orchestration, and continuous observability against emerging agent-era threats.
August 31, 2026
by Uthej Mopathi DZone Core CORE
· 2,430 Views · 3 Likes
article thumbnail
Making User-Generated Sites Embeddable: X-Frame-Options vs CSP Frame-Ancestors
A blank “refused to connect” iframe is usually a legacy X-Frame-Options header. Here is how to make user content embeddable with CSP frame-ancestors and scoped headers.
August 28, 2026
by Ruslan Ianberdin
· 2,062 Views
article thumbnail
Why Your Terraform Drift Alerts Are Useless (And How to Fix Them)
Terraform plan treats all drift equally, creating alert fatigue. A four-tier severity model (critical, high, medium, and low) based on resource type and attributes.
August 27, 2026
by Sudarshan Bhagvan Thakur
· 2,275 Views · 1 Like
article thumbnail
When Guest Access Becomes an Attack Surface: A Technical Analysis of the City-Forum Campaign
Learn how attackers enumerated Salesforce Experience Cloud and ServiceNow portals, and how defenders can detect, audit, and prevent guest-access abuse.
August 27, 2026
by Igboanugo David Ugochukwu DZone Core CORE
· 2,590 Views
article thumbnail
Multi-Account AWS Architecture: Isolating PHI Workloads Without Slowing Down Engineering Teams
Multi-account AWS architecture enforces PHI workload isolation at the boundary level — making access control provable rather than arguable during security reviews.
August 24, 2026
by Garik H
· 2,089 Views
article thumbnail
How to Secure Fintech REST APIs Against BOLA Vulnerabilities
Learn how to protect fintech REST APIs from BOLA attacks with object-level authorization, secure identifiers, access controls, and API security testing.
August 24, 2026
by Nanne Parmar
· 1,408 Views · 1 Like
article thumbnail
Why DAST Findings Are Hard to Fix and How to Make Them Actionable
Here's how repro evidence, ownership mapping, exploitability data, and retesting turn DAST alerts into fixes developers can actually act on.
August 20, 2026
by Philip Piletic DZone Core CORE
· 1,965 Views
article thumbnail
Future-Proofing JWT Security: Crypto-Agility, Post-Quantum Signatures, and IAM Migration
Learn how to prepare JWT and IAM systems for post-quantum security with crypto-agility, safer algorithms, key rotation, and migration strategies for developers.
August 17, 2026
by Ravikanth G
· 1,093 Views · 2 Likes
article thumbnail
5 Infrastructure Controls for Securing AI Agents
Prompt-based guardrails fail under adversarial pressure. Here are the five controls that helps to validate along with the configuration to implement them.
August 14, 2026
by Shekar Munirathnam
· 1,893 Views · 1 Like
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • ...
  • Next
  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook
×