# Audit Report: POS System — Founding Engineer Perspective **Date**: 2026-03-20 **Auditor**: Founding Engineer **Scope**: Code quality, shared patterns, template compliance, DRY violations **Working Directory**: `/Users/velikho/Desktop/WORKING/pos-system` --- ## Executive Summary The GoodGo Platform is a well-structured enterprise microservices monorepo with 23 .NET services, 5 frontend applications, and 6 shared Node.js packages. Clean Architecture and CQRS patterns are consistently applied across all services. However, three critical issues must be resolved before production: insecure CORS configuration (all 23 services), broken health check authentication (18 services), and ~5,100 lines of duplicated MediatR behaviors. --- ## Critical Issues ### C1 — Insecure CORS Configuration (All 23 Services) **Severity**: HIGH — Security vulnerability **Files**: `services/*/src/*/Program.cs` lines ~113–120 Every service uses `AllowAnyOrigin()`: ```csharp // Current (INSECURE) policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); ``` This allows any website to issue cross-origin requests to the API, enabling CSRF attacks. There is no origin validation. `AllowAnyOrigin()` is also incompatible with `AllowCredentials()`, silently breaking cookie/credential flows. **Fix**: ```csharp var allowedOrigins = builder.Configuration .GetSection("Cors:AllowedOrigins") .Get() ?? Array.Empty(); policy.WithOrigins(allowedOrigins) .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); ``` Add `Cors__AllowedOrigins__0=https://goodgo.vn` per service in `docker-compose.yml` and Kubernetes manifests. --- ### C2 — Health Check Endpoints Require Authentication (18 of 23 Services) **Severity**: HIGH — Breaks Kubernetes liveness/readiness probes **Example**: `services/merchant-service-net/src/MerchantService.API/Program.cs` lines 132–137 ```csharp // Current (missing .AllowAnonymous()) app.MapHealthChecks("/health"); app.MapHealthChecks("/health/live", new() { Predicate = _ => false }); app.MapHealthChecks("/health/ready"); ``` Kubernetes kubelet cannot authenticate; probes return `401 Unauthorized` → pod marked unhealthy → restart loop. Docker Compose healthcheck (`curl -f http://localhost:8080/health/live`) also fails. **Services without `.AllowAnonymous()`**: merchant, iam, order, wallet, storage, membership, booking, catalog, inventory, chat, social, mining, mission, promotion, ads-manager, ads-analytics, mkt-facebook, mkt-whatsapp (and others). **Services correctly configured**: ads-serving, ads-tracking, ads-billing, mkt-x, mkt-zalo. **Fix**: ```csharp app.MapHealthChecks("/health").AllowAnonymous(); app.MapHealthChecks("/health/live", new() { Predicate = _ => false }).AllowAnonymous(); app.MapHealthChecks("/health/ready").AllowAnonymous(); ``` --- ### C3 — MediatR Behaviors Duplicated Across All 23 Services **Severity**: HIGH — Maintenance risk; single bug requires 23 fixes **Files**: `services/*/src/*/Application/Behaviors/` Three behavior files are copied verbatim in every service: | File | Lines per copy | Total copies | Total duplicated lines | |------|---------------|--------------|----------------------| | `LoggingBehavior.cs` | ~59 | 23 | ~1,357 | | `ValidatorBehavior.cs` | ~41 | 23 | ~943 | | `TransactionBehavior.cs` | ~50 | 23 | ~1,150 | | **Total** | | | **~3,450** | Example of identical files: - `services/merchant-service-net/src/MerchantService.API/Application/Behaviors/LoggingBehavior.cs` - `services/order-service-net/src/OrderService.API/Application/Behaviors/LoggingBehavior.cs` Only the namespace differs. Any fix in one behavior must be manually propagated to 22 other copies. **Fix**: Extract to a shared NuGet package `GoodGo.MediatR.Behaviors`: ``` packages/ GoodGo.MediatR.Behaviors/ LoggingBehavior.cs ValidatorBehavior.cs TransactionBehavior.cs ``` Reference from each service: ```xml ``` --- ### C4 — JWT Signature Validation Disabled in Development **Severity**: MEDIUM — Allows any token to be accepted in dev environments **Files**: `services/*/src/*/Program.cs` lines ~91–108 ```csharp ValidateIssuerSigningKey = builder.Environment.IsDevelopment() ? false : true, SignatureValidator = builder.Environment.IsDevelopment() ? (token, _) => new JsonWebToken(token) : null, ``` In development, any crafted JWT with valid `exp`/`nbf` claims is accepted. Tokens are not signature-verified. Risk is mitigated by `IsDevelopment()` check but raises the question of why this is needed at all. **Root Cause**: Likely the IAM service (Duende IdentityServer) uses a rotating signing key not pre-shared with other services in Docker Compose local dev. **Fix**: Seed a deterministic development signing key via environment variable and share it across services in `docker-compose.yml`. This enables full signature validation in all environments. --- ## Warnings ### W1 — SeedWork Classes Duplicated Across Domain Layers **Impact**: Medium — Changes to base patterns require multi-service updates **Files**: `services/*/src/*/Domain/SeedWork/` `Entity.cs`, `IAggregateRoot.cs`, `IRepository.cs`, `ValueObject.cs`, `IUnitOfWork.cs`, `Enumeration.cs` are copied into every service's Domain layer. This is partially justified by DDD isolation (Domain layer must have no external NuGet dependencies), but it means fixing a bug in `Entity.cs` requires updates in 23 places. **Recommendation**: Document this as an intentional architectural trade-off in CLAUDE.md (currently undocumented). If the team wants single-source-of-truth, create `GoodGo.Domain.SeedWork` as a source-only NuGet (no DI dependencies). --- ### W2 — API Versioning Configured But Not Fully Utilized **Impact**: Low — Unused NuGet package capability **Files**: `services/*/src/*/Program.cs` lines 42–56 ```csharp builder.Services.AddApiVersioning(options => { options.DefaultApiVersion = new ApiVersion(1, 0); options.AssumeDefaultVersionWhenUnspecified = true; // Bypasses versioning }); ``` Controllers use hardcoded routes (`[Route("api/v1/merchants")]`) rather than the `{version:apiVersion}` parameter. `Asp.Versioning` NuGet is installed but the API version attribute system is not used. Routes work correctly but versioning capability is nullified. **Options**: 1. Accept current pattern (hardcoded v1 routes) and document it 2. Migrate to `[ApiVersion("1.0")]` on controllers and use `{version:apiVersion}` route parameter --- ### W3 — Missing README.md in 14 Services **Impact**: Low — Developer onboarding friction **Services with README**: iam-service-net, merchant-service-net, membership-service-net, mkt-facebook-service-net, mkt-whatsapp-service-net, mkt-x-service-net, mkt-zalo-service-net (7 of 23) **Services without README**: order, wallet, storage, booking, catalog, inventory, chat, social, mining, mission, promotion, ads-manager, ads-serving, ads-tracking, ads-billing, ads-analytics (16 of 23) --- ### W4 — `init-databases.sh` Must Be Manually Maintained **File**: `deployments/local/init-databases.sh` When a new service is created, the database must be manually added to this script. There is no automated enforcement. If a developer creates a new service and forgets this step, the service's database won't be initialized in local dev. **Recommendation**: Add a pre-start validation script or generate the init script from docker-compose.yml service names. --- ### W5 — One Incomplete Test TODO **File**: `services/iam-service-net/tests/IamService.FunctionalTests/Controllers/AuthorizationPolicyTests.cs` ```csharp /// TODO: Configure test authentication to properly inject role claims. ``` Minor — single comment in test code, not production code. But indicates an incomplete test scenario for authorization policies. --- ## Improvements ### I1 — Extract Common Infrastructure to Shared NuGet Packages Priority: High Create a `GoodGo.*` suite of shared NuGet packages: - `GoodGo.MediatR.Behaviors` — LoggingBehavior, ValidatorBehavior, TransactionBehavior - `GoodGo.AspNetCore.Extensions` — Common Program.cs middleware setup - `GoodGo.Domain.SeedWork` *(optional, source-only)* — Entity, ValueObject, IAggregateRoot This eliminates the bulk of cross-service duplication. --- ### I2 — Standardize CORS via Configuration Priority: High Move CORS allowed origins out of code and into configuration. Each service should read from `Cors:AllowedOrigins` environment variable. Docker Compose and Kubernetes manifests set per-environment values. This is a one-time templating effort with high security payoff. --- ### I3 — Add Health Check Tests to All Functional Test Suites Priority: Medium Verify that `/health/live` and `/health/ready` return 200 without authentication. Currently only some functional test suites include these tests. Adding them to all 23 services would catch the C2 issue automatically in CI. ```csharp [Fact] public async Task HealthLive_ReturnsOk_WithoutAuthentication() { var response = await _client.GetAsync("/health/live"); response.StatusCode.Should().Be(HttpStatusCode.OK); } ``` --- ### I4 — Centralize Docker Compose Environment Defaults Priority: Medium Several environment variables are repeated across 23+ service definitions in `docker-compose.yml`. Consider using YAML anchors or a `.env` fragment approach to reduce drift: ```yaml x-common-env: &common-env ASPNETCORE_ENVIRONMENT: Development Serilog__MinimumLevel: Information ConnectionStrings__Redis: redis:6379 ``` --- ### I5 — Add Consistent CI Health Check Validation Priority: Low Add a CI step that calls `/health/live` on each newly-built Docker image before pushing to Docker Hub. This catches health check regressions before staging deployment. --- ## Action Items (Prioritized) | # | Action | Priority | Effort | Owner | |---|--------|----------|--------|-------| | 1 | Fix CORS — replace `AllowAnyOrigin()` with `WithOrigins()` in all 23 services | P0 | 2h | Founding Engineer | | 2 | Fix health check authentication — add `.AllowAnonymous()` to 18 services | P0 | 1h | Founding Engineer | | 3 | Extract MediatR behaviors to `GoodGo.MediatR.Behaviors` NuGet | P1 | 4h | Founding Engineer | | 4 | Fix JWT development signing key — use deterministic key via env var | P1 | 2h | Founding Engineer | | 5 | Add health check tests to all functional test suites | P1 | 3h | QA Engineer | | 6 | Create README.md for 16 missing services | P2 | 8h | Tech Lead | | 7 | Document SeedWork duplication trade-off in CLAUDE.md | P2 | 0.5h | Tech Lead | | 8 — | Resolve IAM authorization test TODO | P2 | 1h | Backend Dev | | 9 | Clarify API versioning strategy and document or clean up | P3 | 1h | CTO | --- ## Architecture Strengths The following areas are well-implemented and should serve as reference patterns: - **Clean Architecture**: All 23 services strictly enforce the Domain → no external dependencies rule - **CQRS**: Commands, Queries, and Handlers follow naming conventions consistently - **Entity Pattern**: Private fields + behavior methods + domain events is correctly applied everywhere - **Test Structure**: Every service has both unit tests (xUnit + Moq + FluentAssertions) and functional tests (WebApplicationFactory + InMemory) - **Dockerfile**: Multi-stage build, non-root user (dotnetuser:1001), health check executable installed, port 8080 - **Traefik**: Path-based routing, rate limiting, secure headers, CORS middleware all configured - **Bilingual**: Comments, logs, validation messages in EN + VI across the entire codebase - **Observability**: Prometheus + Grafana + Loki stack configured in `infra/observability/` - **Monorepo**: pnpm workspaces + Turborepo correctly set up with shared `packages/`