PS Product SecurityKnowledge Base

Argo CD AppProject and Sync Windows

Argo CD Project Boundaries

Intro: Argo CD becomes materially safer when you stop treating the default project as normal and start treating AppProject as your deployment policy boundary. This page shows how to narrow repo trust, destination trust, and change timing so GitOps automation behaves like a controlled release path.

What this page includes

  • what AppProject is and why it matters
  • a production-oriented project manifest with comments
  • sync-window patterns for safer production change control

Working assumptions

  • Git is not automatically trusted just because it is version-controlled
  • production deploy timing is part of release risk, not only an operations concern

Why AppProject matters

AppProject lets you restrict:

  • which Git repositories are trusted;
  • which clusters and namespaces are allowed destinations;
  • which resource kinds may be deployed;
  • which roles can act on applications in that project.

The default Argo CD project starts permissive. That is convenient for demos and dangerous for production.

A safer AppProject baseline

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: payments-prod
  namespace: argocd
spec:
  description: "Production project for payment-facing services"

  # Trust only the repositories that are allowed to define this environment.
  sourceRepos:
    - 'https://gitlab.example.com/platform/payments-manifests.git'
    - 'https://gitlab.example.com/platform/shared-helm.git'
    - '!https://gitlab.example.com/**/personal-*'

  # Limit where this project can deploy.
  destinations:
    - namespace: payments-prod
      server: https://kubernetes.default.svc
    - namespace: payments-shared
      server: https://kubernetes.default.svc

  # Allow only selected cluster-scoped resources.
  clusterResourceWhitelist:
    - group: ''
      kind: Namespace
    - group: networking.k8s.io
      kind: NetworkPolicy

  # Allow namespaced resources needed by the product.
  namespaceResourceWhitelist:
    - group: apps
      kind: Deployment
    - group: ''
      kind: Service
    - group: autoscaling
      kind: HorizontalPodAutoscaler
    - group: batch
      kind: Job
    - group: networking.k8s.io
      kind: Ingress

  # Example role mapped to an SSO group.
  roles:
    - name: deployers
      description: "Can sync payment applications"
      groups:
        - oidc:payments-platform
      policies:
        - p, proj:payments-prod:deployers, applications, get, payments-prod/*, allow
        - p, proj:payments-prod:deployers, applications, sync, payments-prod/*, allow
        - p, proj:payments-prod:deployers, applications, action/*, payments-prod/*, allow

  # Allow prod sync only in defined windows, with manual override behavior explicit.
  syncWindows:
    - kind: allow
      schedule: '0 18 * * 1-4'
      duration: 2h
      applications:
        - 'payments-*'
      manualSync: true
    - kind: deny
      schedule: '0 0 * * 5'
      duration: 24h
      applications:
        - 'payments-*'

What this configuration is doing

Control Why it matters
narrow sourceRepos stops unrelated repos from becoming deployment inputs
explicit destinations prevents project sprawl into arbitrary namespaces or clusters
resource allowlists limits high-impact object kinds
project roles ties GitOps actions to named groups
sync windows makes risky deployment times explicit

Sync window patterns

Production office-hours allow window

syncWindows:
  - kind: allow
    schedule: '0 18 * * 1-4'
    duration: 2h
    applications:
      - 'prod-*'
    manualSync: true

Use when you want automated or manual syncs only during an approved change window.

Weekend deny window

syncWindows:
  - kind: deny
    schedule: '0 0 * * 5'
    duration: 72h
    applications:
      - 'prod-*'

Use when you want to suppress risky production churn during weekends or thin staffing periods.

Namespace-scoped deny rule

syncWindows:
  - kind: deny
    schedule: '0 22 * * *'
    duration: 8h
    namespaces:
      - payments-prod

Use when the risk boundary is the namespace rather than a specific application naming pattern.

CLI examples

# Create a project with one repo and one destination.
argocd proj create payments-prod   -d https://kubernetes.default.svc,payments-prod   -s https://gitlab.example.com/platform/payments-manifests.git

# Add an allow sync window.
argocd proj windows add payments-prod   --kind allow   --schedule "0 18 * * 1-4"   --duration 2h   --applications "payments-*"

# Permit manual sync during the window.
argocd proj windows enable-manual-sync payments-prod 0

Common mistakes

Mistake Why it hurts Better pattern
leaving apps in default forever default project is intentionally permissive create dedicated projects early
trusting all repos with '*' any repo can become deployment input allow specific repos, deny known-bad patterns
allowing all resource kinds hidden privilege expansion in manifests whitelist what the product actually needs
no sync windows for prod deploy timing stays accidental define allow/deny windows and exception rules
no project roles operational access stays broad map project actions to named groups

Author attribution: Ivan Piskunov, 2026 - Educational and defensive-engineering use.