Skip to main content
Updated Feb 23, 2026

Capstone: Secure Task API

You've spent this chapter building security knowledge piece by piece: the 4C model in Lesson 1, RBAC in Lesson 2, NetworkPolicies in Lesson 3, secrets in Lesson 4, PSS in Lesson 5, image scanning in Lesson 6, Dapr security in Lesson 7, and compliance mapping in Lesson 8. Now you apply everything together.

In December 2023, a startup lost their Series A funding when a security audit discovered their "production-ready" Kubernetes deployment was running pods as root, had no network segmentation, and stored database credentials in environment variables visible to any cluster user. The technical fix took two days. The reputation damage took six months to repair.

This capstone produces a Task API deployment that would pass that audit. You'll write a security specification first, compose all security controls into a unified deployment, execute a 10-point audit, run penetration test scenarios to verify controls work, and document your security posture for compliance evidence.


Security Specification (Write This First)

Before implementing anything, define what "secure" means for your Task API. This specification becomes your acceptance criteria.

Task API Security Specification

Application: Task API (FastAPI + SQLModel) Target Environment: Production Kubernetes namespace Compliance Context: SOC2 Type II preparation

Security Controls Required:

#ControlSuccess CriteriaVerification Method
1Dedicated ServiceAccountPods run as task-api-sa, not defaultkubectl get pod -o jsonpath='{.spec.serviceAccountName}'
2Minimal RBAC permissionsSA can only get/list ConfigMaps in own namespacekubectl auth can-i returns no for secrets, other namespaces
3Default-deny NetworkPolicyAll ingress/egress blocked except explicit allowlistsTest pod cannot reach Task API without matching labels
4DNS egress allowedPods can resolve Kubernetes servicesnslookup kubernetes.default succeeds from pod
5Secrets via volume mountNo secrets in env vars, mounted at /secrets/kubectl get pod -o yaml shows no env.valueFrom.secretKeyRef
6PSS Restricted complianceNamespace enforces Restricted profilekubectl apply --dry-run=server accepts deployment
7No CRITICAL vulnerabilitiesTrivy scan passes with --exit-code 1 --severity CRITICALExit code 0
8Dapr mTLS enabledSentry running, certificates validdapr status -k shows healthy Sentry
9Component scopes configuredStatestore only accessible by task-apiOther app-ids receive ERR_STATE_STORE_NOT_FOUND
10Audit logging enabledDapr API logging captures all requestsdapr.io/enable-api-logging: "true" annotation present

Non-Goals (explicitly excluded):

  • TLS termination at ingress (handled by Gateway API in Ch56)
  • JWT authentication (handled by SecurityPolicy in Ch56)
  • Multi-cluster federation (Chapter 61)

Compose All Security Components

Apply the security components you've built throughout this chapter. Each file references patterns from its source lesson.

Step 1: Namespace with PSS Labels (L05)

Create 01-namespace.yaml:

apiVersion: v1
kind: Namespace
metadata:
name: task-api
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest

Step 2: RBAC (L02)

Create 02-rbac.yaml:

---
apiVersion: v1
kind: ServiceAccount
metadata:
name: task-api-sa
namespace: task-api
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: task-api-role
namespace: task-api
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: task-api-binding
namespace: task-api
subjects:
- kind: ServiceAccount
name: task-api-sa
namespace: task-api
roleRef:
kind: Role
name: task-api-role
apiGroup: rbac.authorization.k8s.io

Step 3: NetworkPolicy (L03)

Create 03-network-policy.yaml:

---
# Default deny all traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: task-api
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# Allow DNS egress for service discovery
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: task-api
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
---
# Allow ingress from gateway
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-gateway-ingress
namespace: task-api
spec:
podSelector:
matchLabels:
app: task-api
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: envoy-gateway-system
ports:
- protocol: TCP
port: 8000
---
# Allow egress to Redis statestore
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-redis-egress
namespace: task-api
spec:
podSelector:
matchLabels:
app: task-api
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
---
# Allow egress to Dapr sidecar (localhost)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dapr-sidecar
namespace: task-api
spec:
podSelector:
matchLabels:
app: task-api
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: task-api
ports:
- protocol: TCP
port: 3500
- protocol: TCP
port: 50001

Step 4: Secrets Management (L04)

Create 04-secrets.yaml:

apiVersion: v1
kind: Secret
metadata:
name: task-api-secrets
namespace: task-api
type: Opaque
data:
database-url: cG9zdGdyZXNxbDovL3VzZXI6cGFzc0BkYi90YXNrcw== # base64 encoded
redis-password: cmVkaXMtc2VjcmV0LXBhc3N3b3Jk # base64 encoded

Step 5: Dapr Components with Scopes (L07)

Create 05-dapr-components.yaml:

---
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
namespace: task-api
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: redis:6379
- name: redisPassword
secretKeyRef:
name: task-api-secrets
key: redis-password
scopes:
- task-api # Only task-api can access this component

Step 6: Task API Deployment (All Layers)

Create 06-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
name: task-api
namespace: task-api
spec:
replicas: 3
selector:
matchLabels:
app: task-api
template:
metadata:
labels:
app: task-api
annotations:
# Dapr sidecar configuration
dapr.io/enabled: "true"
dapr.io/app-id: "task-api"
dapr.io/app-port: "8000"
# Security hardening (L07)
dapr.io/sidecar-listen-addresses: "127.0.0.1"
dapr.io/enable-api-logging: "true"
spec:
serviceAccountName: task-api-sa
automountServiceAccountToken: false

# Pod-level security context (L05)
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault

containers:
- name: task-api
image: ghcr.io/org/task-api:v1.0.0@sha256:abc123... # Digest pinned (L06)
ports:
- containerPort: 8000

# Container-level security context (L05)
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL

resources:
limits:
memory: "256Mi"
cpu: "500m"
requests:
memory: "128Mi"
cpu: "100m"

# Secrets via volume mount, not env vars (L04)
volumeMounts:
- name: secrets
mountPath: /secrets
readOnly: true
- name: tmp
mountPath: /tmp

volumes:
- name: secrets
secret:
secretName: task-api-secrets
- name: tmp
emptyDir: {}

Apply All Components

kubectl apply -f 01-namespace.yaml
kubectl apply -f 02-rbac.yaml
kubectl apply -f 03-network-policy.yaml
kubectl apply -f 04-secrets.yaml
kubectl apply -f 05-dapr-components.yaml
kubectl apply -f 06-deployment.yaml

Output:

namespace/task-api created
serviceaccount/task-api-sa created
role.rbac.authorization.k8s.io/task-api-role created
rolebinding.rbac.authorization.k8s.io/task-api-binding created
networkpolicy.networking.k8s.io/default-deny-all created
networkpolicy.networking.k8s.io/allow-dns-egress created
networkpolicy.networking.k8s.io/allow-gateway-ingress created
networkpolicy.networking.k8s.io/allow-redis-egress created
networkpolicy.networking.k8s.io/allow-dapr-sidecar created
secret/task-api-secrets created
component.dapr.io/statestore created
deployment.apps/task-api created

10-Point Security Audit

Execute each audit check with its verification command. All items must pass before declaring the deployment production-ready.

Audit Check 1: Dedicated ServiceAccount

Command:

kubectl get pods -n task-api -l app=task-api -o jsonpath='{.items[0].spec.serviceAccountName}'

Expected Output:

task-api-sa

Pass Criteria: Output is task-api-sa, not default.


Audit Check 2: Minimal RBAC Permissions

Allowed operations:

kubectl auth can-i get configmaps \
--as=system:serviceaccount:task-api:task-api-sa \
-n task-api

Expected Output:

yes

Denied operations:

kubectl auth can-i get secrets \
--as=system:serviceaccount:task-api:task-api-sa \
-n task-api

Expected Output:

no
kubectl auth can-i get configmaps \
--as=system:serviceaccount:task-api:task-api-sa \
-n default

Expected Output:

no

Pass Criteria: ConfigMaps allowed in own namespace, secrets denied, other namespaces denied.


Audit Check 3: Default-Deny NetworkPolicy

Command:

kubectl get networkpolicy -n task-api default-deny-all -o jsonpath='{.spec.policyTypes}'

Expected Output:

["Ingress","Egress"]

Pass Criteria: Both Ingress and Egress policy types present with empty selectors (default deny).


Audit Check 4: DNS Egress Allowed

Command (run from within a Task API pod):

kubectl exec -n task-api deploy/task-api -c task-api -- nslookup kubernetes.default

Expected Output:

Server:    10.96.0.10
Address: 10.96.0.10#53

Name: kubernetes.default.svc.cluster.local
Address: 10.96.0.1

Pass Criteria: DNS resolution succeeds despite default-deny egress.


Audit Check 5: Secrets via Volume Mount

Command:

kubectl get pods -n task-api -l app=task-api -o yaml | grep -A5 "env:" | grep secretKeyRef || echo "No secretKeyRef in env vars"

Expected Output:

No secretKeyRef in env vars

Verify volume mount exists:

kubectl get pods -n task-api -l app=task-api -o jsonpath='{.items[0].spec.containers[0].volumeMounts[?(@.name=="secrets")].mountPath}'

Expected Output:

/secrets

Pass Criteria: No secretKeyRef in environment variables, secrets mounted at /secrets.


Audit Check 6: PSS Restricted Compliance

Command:

kubectl apply -f 06-deployment.yaml --dry-run=server

Expected Output:

deployment.apps/task-api created (server dry run)

Pass Criteria: No PSS violations reported, deployment accepted.


Audit Check 7: No CRITICAL Vulnerabilities

Command:

trivy image ghcr.io/org/task-api:v1.0.0 --severity CRITICAL --exit-code 1

Expected Output:

ghcr.io/org/task-api:v1.0.0 (debian 12.5)
=========================================
Total: 0 (CRITICAL: 0)

Pass Criteria: Exit code 0, zero CRITICAL vulnerabilities.


Audit Check 8: Dapr mTLS Enabled

Command:

dapr status -k | grep dapr-sentry

Expected Output:

dapr-sentry            dapr-system  True     Running  1         1.12.0   7d   2024-01-15 10:23:45

Verify certificate validity:

kubectl get pods -n dapr-system -l app=dapr-sentry -o name | head -1 | \
xargs -I {} kubectl exec {} -n dapr-system -- \
openssl x509 -in /var/run/secrets/dapr.io/tls/ca.crt -noout -enddate

Expected Output:

notAfter=Jan 15 10:23:45 2025 GMT

Pass Criteria: Sentry healthy, certificate not expired.


Audit Check 9: Component Scopes Configured

Command (from a pod with different app-id):

# Create test pod with different app-id
kubectl run test-attacker -n task-api --image=curlimages/curl:latest \
--annotations="dapr.io/enabled=true,dapr.io/app-id=attacker" \
--command -- sleep 3600

# Attempt to access statestore
kubectl exec -n task-api test-attacker -c daprd -- \
curl -s http://localhost:3500/v1.0/state/statestore

Expected Output:

{
"errorCode": "ERR_STATE_STORE_NOT_FOUND",
"message": "state store statestore is not found"
}

Pass Criteria: Non-scoped app-ids cannot see the statestore.

Cleanup:

kubectl delete pod test-attacker -n task-api

Audit Check 10: Audit Logging Enabled

Command:

kubectl get pods -n task-api -l app=task-api -o jsonpath='{.items[0].metadata.annotations.dapr\.io/enable-api-logging}'

Expected Output:

true

Pass Criteria: Annotation present and set to "true".


Audit Summary Script

Combine all checks into an executable audit script:

#!/bin/bash
# secure-task-api-audit.sh

echo "=== Task API Security Audit ==="
echo ""

PASS=0
FAIL=0

# Check 1: Dedicated ServiceAccount
SA=$(kubectl get pods -n task-api -l app=task-api -o jsonpath='{.items[0].spec.serviceAccountName}' 2>/dev/null)
if [ "$SA" = "task-api-sa" ]; then
echo "[PASS] 1. Dedicated ServiceAccount: $SA"
((PASS++))
else
echo "[FAIL] 1. Dedicated ServiceAccount: got '$SA', expected 'task-api-sa'"
((FAIL++))
fi

# Check 2: Minimal RBAC (test denied operation)
SECRETS=$(kubectl auth can-i get secrets --as=system:serviceaccount:task-api:task-api-sa -n task-api 2>/dev/null)
if [ "$SECRETS" = "no" ]; then
echo "[PASS] 2. Minimal RBAC: secrets access denied"
((PASS++))
else
echo "[FAIL] 2. Minimal RBAC: secrets access should be denied"
((FAIL++))
fi

# Check 3: Default-deny NetworkPolicy
NP=$(kubectl get networkpolicy -n task-api default-deny-all -o jsonpath='{.spec.policyTypes}' 2>/dev/null)
if [[ "$NP" == *"Ingress"* ]] && [[ "$NP" == *"Egress"* ]]; then
echo "[PASS] 3. Default-deny NetworkPolicy: both Ingress and Egress"
((PASS++))
else
echo "[FAIL] 3. Default-deny NetworkPolicy: missing policy types"
((FAIL++))
fi

# Check 4: DNS egress (skip in automated script, requires pod exec)
echo "[SKIP] 4. DNS egress: requires manual verification with nslookup"

# Check 5: Secrets via volume mount
SECRETENV=$(kubectl get pods -n task-api -l app=task-api -o yaml 2>/dev/null | grep secretKeyRef || echo "none")
if [ "$SECRETENV" = "none" ]; then
echo "[PASS] 5. Secrets via volume mount: no secretKeyRef in env"
((PASS++))
else
echo "[FAIL] 5. Secrets via volume mount: found secretKeyRef in env vars"
((FAIL++))
fi

# Check 6: PSS Restricted compliance
PSS=$(kubectl apply -f 06-deployment.yaml --dry-run=server 2>&1)
if [[ "$PSS" != *"Forbidden"* ]]; then
echo "[PASS] 6. PSS Restricted compliance: deployment accepted"
((PASS++))
else
echo "[FAIL] 6. PSS Restricted compliance: violations detected"
((FAIL++))
fi

# Check 7: No CRITICAL vulnerabilities (skip in quick audit)
echo "[SKIP] 7. No CRITICAL vulnerabilities: run 'trivy image' manually"

# Check 8: Dapr mTLS enabled
SENTRY=$(dapr status -k 2>/dev/null | grep dapr-sentry | grep Running)
if [ -n "$SENTRY" ]; then
echo "[PASS] 8. Dapr mTLS: Sentry running"
((PASS++))
else
echo "[FAIL] 8. Dapr mTLS: Sentry not running"
((FAIL++))
fi

# Check 9: Component scopes (skip in automated script)
echo "[SKIP] 9. Component scopes: requires test pod with different app-id"

# Check 10: Audit logging enabled
LOGGING=$(kubectl get pods -n task-api -l app=task-api -o jsonpath='{.items[0].metadata.annotations.dapr\.io/enable-api-logging}' 2>/dev/null)
if [ "$LOGGING" = "true" ]; then
echo "[PASS] 10. Audit logging: enabled"
((PASS++))
else
echo "[FAIL] 10. Audit logging: not enabled"
((FAIL++))
fi

echo ""
echo "=== Audit Results ==="
echo "PASSED: $PASS"
echo "FAILED: $FAIL"
echo "SKIPPED: 4 (manual verification required)"

if [ $FAIL -eq 0 ]; then
echo ""
echo "Automated checks PASSED. Complete manual checks 4, 7, 9 before production."
else
echo ""
echo "AUDIT FAILED. Fix issues before production deployment."
exit 1
fi

Penetration Test Scenarios

Security controls are only valuable if they actually prevent attacks. These scenarios verify your controls work against realistic attack vectors.

Scenario 1: RBAC Bypass Attempt

Attack: Compromised pod attempts to read secrets across namespaces.

Setup:

# Deploy attacker pod that tries to access Kubernetes API
kubectl run attacker -n task-api --image=bitnami/kubectl:latest \
--serviceaccount=task-api-sa \
--command -- sleep 3600

Attack execution:

# Attempt to read secrets
kubectl exec -n task-api attacker -- kubectl get secrets -n task-api

# Attempt to access other namespace
kubectl exec -n task-api attacker -- kubectl get pods -n kube-system

Expected Result (Control Working):

Error from server (Forbidden): secrets is forbidden: User "system:serviceaccount:task-api:task-api-sa" cannot list resource "secrets" in API group "" in the namespace "task-api"
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:task-api:task-api-sa" cannot list resource "pods" in API group "" in the namespace "kube-system"

Cleanup:

kubectl delete pod attacker -n task-api

Scenario 2: Network Escape Attempt

Attack: Compromised pod attempts to reach services outside allowed NetworkPolicies.

Setup:

# Deploy attacker pod
kubectl run network-attacker -n task-api --image=curlimages/curl:latest \
--command -- sleep 3600

Attack execution:

# Attempt to reach external internet
kubectl exec -n task-api network-attacker -- curl -s --max-time 5 https://google.com

# Attempt to reach pods in other namespaces
kubectl exec -n task-api network-attacker -- curl -s --max-time 5 http://kubernetes-dashboard.kubernetes-dashboard.svc:443

Expected Result (Control Working):

curl: (28) Connection timed out after 5001 milliseconds

Cleanup:

kubectl delete pod network-attacker -n task-api

Scenario 3: Privilege Escalation Attempt

Attack: Pod attempts to run privileged container or escape PSS restrictions.

Setup: Create privileged-attacker.yaml:

apiVersion: v1
kind: Pod
metadata:
name: priv-attacker
namespace: task-api
spec:
containers:
- name: attacker
image: nginx:latest
securityContext:
privileged: true

Attack execution:

kubectl apply -f privileged-attacker.yaml

Expected Result (Control Working):

Error from server (Forbidden): error when creating "privileged-attacker.yaml": pods "priv-attacker" is forbidden: violates PodSecurity "restricted:latest": privileged (container "attacker" must not set securityContext.privileged=true)

Security Posture Documentation

Create documentation for compliance auditors. This template maps your controls to common compliance frameworks.

Security Posture Document Template

# Task API Security Posture

**Application**: Task API v1.0.0
**Environment**: Production (task-api namespace)
**Audit Date**: [DATE]
**Auditor**: [NAME]

## Executive Summary

The Task API deployment implements defense-in-depth security controls aligned with Kubernetes security best practices and [SOC2/HIPAA] compliance requirements.

## Control Inventory

| Control Category | Control | Implementation | Evidence Location |
|-----------------|---------|----------------|-------------------|
| Identity | Dedicated ServiceAccount | task-api-sa with automount disabled | 02-rbac.yaml |
| Authorization | RBAC least privilege | ConfigMap read-only | 02-rbac.yaml, audit check 2 |
| Network | Default-deny NetworkPolicy | Egress/Ingress blocked | 03-network-policy.yaml |
| Network | Explicit allowlists | DNS, Gateway, Redis only | 03-network-policy.yaml |
| Secrets | Volume-mounted secrets | No env var exposure | 04-secrets.yaml, 06-deployment.yaml |
| Container | PSS Restricted profile | runAsNonRoot, drop ALL caps | 01-namespace.yaml, 06-deployment.yaml |
| Supply Chain | Image scanning | Trivy CRITICAL gate | CI/CD workflow |
| Supply Chain | Digest pinning | Immutable image references | 06-deployment.yaml |
| Encryption | Dapr mTLS | Sidecar-to-sidecar encryption | Dapr installation |
| Isolation | Component scopes | Statestore restricted to task-api | 05-dapr-components.yaml |
| Audit | API logging | Dapr request logging | 06-deployment.yaml annotation |

## Compliance Mapping

### SOC2 Criteria Coverage

| SOC2 Criteria | Control Implemented | Evidence |
|---------------|---------------------|----------|
| CC6.1 Logical access | RBAC, ServiceAccount | Audit check 1, 2 |
| CC6.6 System boundaries | NetworkPolicy default-deny | Audit check 3 |
| CC6.7 Restrict transmission | Dapr mTLS, TLS | Audit check 8 |
| CC7.2 Monitor for unauthorized access | API logging | Audit check 10 |

### HIPAA Technical Safeguards

| HIPAA Section | Control Implemented | Evidence |
|---------------|---------------------|----------|
| 164.312(a)(1) Access control | RBAC, PSS | Audit checks 1, 2, 6 |
| 164.312(e)(1) Transmission security | mTLS, NetworkPolicy | Audit checks 3, 8 |
| 164.312(b) Audit controls | Dapr API logging | Audit check 10 |

## Penetration Test Results

| Scenario | Attack Vector | Result | Control Validated |
|----------|---------------|--------|-------------------|
| RBAC Bypass | SA accessing secrets | BLOCKED | RBAC least privilege |
| Network Escape | Pod reaching external | BLOCKED | NetworkPolicy default-deny |
| Privilege Escalation | Privileged container | BLOCKED | PSS Restricted |

## Recommendations

1. **Immediate**: None - all controls passing
2. **Short-term**: Implement OPA Gatekeeper for policy-as-code enforcement
3. **Long-term**: Add runtime security monitoring (Falco)

## Audit Trail

- 10-point audit script: `secure-task-api-audit.sh`
- Trivy scan report: `trivy-results.sarif`
- SBOM: `sbom-spdx.json`

Try With AI

Test your ability to design, implement, and audit production security configurations.

Prompt 1:

Write a security specification for a payment-processing microservice that:
- Handles PCI-DSS data
- Needs database connectivity
- Uses Dapr for state management
- Runs in a shared Kubernetes cluster

Define the 10-point audit checklist with specific success criteria.

What you're learning: Security specification design requires understanding threat models before implementation. PCI-DSS adds requirements beyond standard production security: encrypted data at rest, stricter network segmentation, key rotation policies. Notice how your specification changes when compliance requirements increase.

Prompt 2:

My Task API passed 9/10 audit checks, but check 7 (Trivy scan) found
3 HIGH vulnerabilities in the Python base image. The vulnerabilities
are in packages my application doesn't directly use. Should I:
A) Switch to Alpine/Distroless base image
B) Accept the risk and document it
C) Wait for upstream fixes
D) Something else?

Walk me through the decision framework.

What you're learning: Security is about risk management, not zero vulnerabilities. The decision depends on: Is the vulnerable code reachable? Is there a known exploit? What's the remediation timeline? Distroless images reduce attack surface but may break your application. This requires analyzing tradeoffs, not following rules.

Prompt 3:

Design a penetration test scenario that validates Dapr component scopes
are working correctly. The scenario should:
- Create an "attacker" workload in the same namespace
- Attempt to access multiple Dapr components
- Produce clear pass/fail evidence
- Be safe to run in a shared environment

What you're learning: Penetration testing validates that security controls actually work. The key insight is that scopes are enforced by app-id, so your test needs a pod with a different Dapr app-id annotation. The test should be non-destructive (read attempts, not writes) and cleanup after itself.

Security Reminder

A 10-point audit provides evidence that controls are configured correctly at a point in time. Production security requires continuous monitoring: alert on RBAC changes, track NetworkPolicy modifications, scan images on schedule. This capstone establishes your security baseline; operational practices maintain it.