{"id":51631057,"url":"https://github.com/semgrep/semgrep-policies-gitops","last_synced_at":"2026-07-13T07:03:42.601Z","repository":{"id":364175393,"uuid":"1266754287","full_name":"semgrep/semgrep-policies-gitops","owner":"semgrep","description":"Reference implementation: manage Semgrep detection \u0026 remediation policies as code with GitOps, using the Policies V2 API.","archived":false,"fork":false,"pushed_at":"2026-07-07T22:42:33.000Z","size":70,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-08T00:13:07.111Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/semgrep.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-11T23:27:35.000Z","updated_at":"2026-07-07T22:42:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/semgrep/semgrep-policies-gitops","commit_stats":null,"previous_names":["kmoses228/semgrep-policies-gitops","semgrep/semgrep-policies-gitops"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/semgrep/semgrep-policies-gitops","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semgrep%2Fsemgrep-policies-gitops","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semgrep%2Fsemgrep-policies-gitops/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semgrep%2Fsemgrep-policies-gitops/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semgrep%2Fsemgrep-policies-gitops/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/semgrep","download_url":"https://codeload.github.com/semgrep/semgrep-policies-gitops/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semgrep%2Fsemgrep-policies-gitops/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35413543,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-13T02:00:06.543Z","response_time":119,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-13T07:03:42.074Z","updated_at":"2026-07-13T07:03:42.595Z","avatar_url":"https://github.com/semgrep.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Semgrep policies as code (GitOps reference)\n\nA worked example of managing [Semgrep](https://semgrep.dev) detection and\nremediation policies with GitOps, using the **Policies V2 API**. Your policy\nstate lives here as YAML; a reconciler reads it, previews the diff, and\nstrictly applies it to your deployment. PRs are gated on the diff, a merge to\n`main` applies, and a nightly read-only check flags out-of-band UI changes.\n\nFork it, point it at your deployment, and adapt the policy files. It has no\ndependency on Semgrep internals; it only calls the public, documented API.\n\n## Why GitOps for policies\n\nThe API is built for a Kubernetes-style apply loop:\n\n1. **Read** the current state (`GET`); it returns a bundle plus a\n   `state_version` etag.\n2. **Plan** by dry-running your bundle (`POST .../:dryRun`); it returns the\n   exact creates / updates / deletes an apply would make, changing nothing.\n3. **Apply** strictly (`PUT` with `If-Match: \u003cstate_version\u003e`); the bundle\n   replaces the live state, removed items are deleted, and a concurrent UI\n   edit is caught as a `409` rather than silently clobbered.\n\nThe server owns the diff, slug handling, and concurrency control, so the\nclient stays thin.\n\n## Layout\n\n```\npolicies/\n  detection-code.yaml      which Semgrep Code rules run, and per-project carve-outs\n  detection-secrets.yaml   same for Semgrep Secrets (optional; omit if not enabled)\n  remediation.yaml         what happens when a finding matches (block, comment, ...)\nreconciler/                the GitOps client + CLI (export / plan / apply)\n.github/workflows/         plan on PRs, apply on merge + nightly\n```\n\n## Quick start\n\n```bash\nuv sync\nexport SEMGREP_API_TOKEN=\"\u003cyour web API token\u003e\"   # never commit this\n\n# Bootstrap the YAML from your live deployment:\nuv run python -m reconciler.cli export --deployment-id \u003cid\u003e\n\n# Edit policies/*.yaml, then check them offline (no token needed):\nuv run python -m reconciler.cli validate\n\n# Preview what an apply would change:\nuv run python -m reconciler.cli plan --deployment-id \u003cid\u003e\n\n# Apply (this is what CI runs on merge):\nuv run python -m reconciler.cli apply --deployment-id \u003cid\u003e\n```\n\n`validate` is offline and structural; `plan` adds the server-side semantic\ncheck and prints the diff. On the nightly check, `plan --fail-on-diff` exits\nnon-zero when the live state has drifted from this repo.\n\n## The policy files\n\n### Detection (`detection-code.yaml`, `detection-secrets.yaml`)\n\nOne bundle per product. `rulesets` are registry ruleset paths (`p/...`),\n`rules` are individual rule paths, `disabled` turns off rules from the\nselected rulesets, and `exceptions` are per-project or per-tag\ninclude/exclude carve-outs. Secrets bundles do not accept `rulesets`.\n\n### Remediation (`remediation.yaml`)\n\nA list of policies, each a filter (conditions over finding attributes) plus\nactions. `slug` is the stable identity: omit it on a new policy to derive it\nfrom the name, and renaming a policy keeps its slug. Some actions require a\ncompanion; `block` must be paired with `pr_comment` in the same policy.\n\nThe accepted condition types, action types, and value enums are published by\nthe API itself:\n\n```bash\ncurl -H \"Authorization: Bearer $SEMGREP_API_TOKEN\" \\\n  \"$SEMGREP_APP_URL/api/policies/v2/deployments/\u003cid\u003e/vocab?product=remediation\"\n```\n\nValidate your YAML against that vocabulary in CI to catch typos before an apply.\n\n## CI setup\n\nThe workflows expect two repository settings:\n\n- **Secret** `SEMGREP_API_TOKEN`: a Semgrep web API token, stored as an\n  Actions secret and never read from the repo.\n- **Variable** `SEMGREP_DEPLOYMENT_ID`: your numeric deployment id.\n\nThe PR checks are layered, cheapest first:\n\n- `validate.yml` runs on PRs that touch `policies/` and needs no token, so it\n  works on fork PRs. It parses the YAML and checks its shape offline, catching\n  malformed YAML or a wrong-shaped policy before any network call.\n- `plan.yml` dry-runs the PR's policies against the live deployment and prints\n  the diff. A pending diff is the change under review, so it passes; the API's\n  semantic validation fails it on a bad **value** or reference (an unknown\n  rule, a typo'd severity, `block` without `pr_comment`).\n- `test.yml` runs the reconciler's unit tests, scoped to PRs touching\n  `reconciler/`, `tests/`, or the dependencies.\n- `apply.yml` runs on every merge to `main` (and on demand); this is the only\n  path that writes.\n- `drift.yml` runs nightly (and on demand), read-only: `plan --fail-on-diff`\n  fails if the live state was changed in the UI out of band, but never writes.\n\nSo `validate` catches structural mistakes offline, while `plan` catches\nsemantic ones (values, references, action dependencies) that only the server\ncan judge. Every action is pinned to a full commit SHA.\n\n## Requirements\n\n- The deployment must be on Semgrep's Unified Policies model, or the API\n  returns `403 DEPLOYMENT_NOT_MIGRATED`.\n- The token must be a web API token, not a browser session.\n\n## Error codes worth handling\n\nThe API returns stable, machine-readable error codes. The reconciler maps the\ncommon ones to actionable messages:\n\n| Code | Meaning |\n| --- | --- |\n| `STATE_VERSION_MISMATCH` (409) | the live state changed since you read it; re-export and retry |\n| `IF_MATCH_REQUIRED` (428) | an apply was sent without the `If-Match` etag |\n| `UNKNOWN_REFERENCE` (400) | a referenced project, tag, rule, or ruleset does not exist |\n| `MISSING_DEPENDENT_ACTION` (400) | an action is missing a required companion (e.g. `block` without `pr_comment`) |\n| `RESERVED_SLUG` (409) | a policy slug collides with a system-managed policy |\n| `PRODUCT_NOT_ENABLED` (404) | the product (e.g. Secrets) is not enabled for the deployment |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemgrep%2Fsemgrep-policies-gitops","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsemgrep%2Fsemgrep-policies-gitops","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemgrep%2Fsemgrep-policies-gitops/lists"}