agentspm 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. agentspm-0.1.1/.env.example +74 -0
  2. agentspm-0.1.1/.githooks/pre-commit +68 -0
  3. agentspm-0.1.1/.githooks/pre-push +63 -0
  4. agentspm-0.1.1/.github/workflows/ci.yml +194 -0
  5. agentspm-0.1.1/.github/workflows/release.yml +150 -0
  6. agentspm-0.1.1/.gitignore +32 -0
  7. agentspm-0.1.1/.trivyignore +16 -0
  8. agentspm-0.1.1/.vuln-scan-config.yaml +55 -0
  9. agentspm-0.1.1/CHANGELOG.md +53 -0
  10. agentspm-0.1.1/Containerfile +108 -0
  11. agentspm-0.1.1/LICENSE +202 -0
  12. agentspm-0.1.1/PKG-INFO +177 -0
  13. agentspm-0.1.1/README.md +154 -0
  14. agentspm-0.1.1/SECURITY.md +43 -0
  15. agentspm-0.1.1/contracts/CHANGELOG.md +29 -0
  16. agentspm-0.1.1/contracts/gateway/v0.1/openapi.yaml +342 -0
  17. agentspm-0.1.1/deploy/agentspm.example.yaml +114 -0
  18. agentspm-0.1.1/deploy/cronjob.example.yaml +80 -0
  19. agentspm-0.1.1/docs/ARCHITECTURE.md +166 -0
  20. agentspm-0.1.1/docs/CONTROLS.md +388 -0
  21. agentspm-0.1.1/docs/GOVERNANCE.md +40 -0
  22. agentspm-0.1.1/docs/INTEGRATION.md +236 -0
  23. agentspm-0.1.1/docs/LANGUAGE.md +66 -0
  24. agentspm-0.1.1/docs/LICENSE-CC-BY-4.0.txt +396 -0
  25. agentspm-0.1.1/docs/PRIVACY.md +216 -0
  26. agentspm-0.1.1/docs/RELEASE-GATE.md +78 -0
  27. agentspm-0.1.1/docs/THREAT-MODEL.md +50 -0
  28. agentspm-0.1.1/docs/adr/ADR-0001-modular-cli-pipeline.md +52 -0
  29. agentspm-0.1.1/docs/adr/ADR-0002-contract-first-gateway.md +38 -0
  30. agentspm-0.1.1/docs/adr/ADR-0003-ai-act-core-block.md +39 -0
  31. agentspm-0.1.1/docs/adr/ADR-0004-agent-identity-derivation.md +90 -0
  32. agentspm-0.1.1/policies/v0.1/capabilities.json +5098 -0
  33. agentspm-0.1.1/policies/v0.1/controls.yaml +615 -0
  34. agentspm-0.1.1/policies/v0.1/fixtures/data_001.yaml +80 -0
  35. agentspm-0.1.1/policies/v0.1/fixtures/data_002.yaml +120 -0
  36. agentspm-0.1.1/policies/v0.1/fixtures/data_003.yaml +81 -0
  37. agentspm-0.1.1/policies/v0.1/fixtures/egress_001.yaml +67 -0
  38. agentspm-0.1.1/policies/v0.1/fixtures/egress_002.yaml +46 -0
  39. agentspm-0.1.1/policies/v0.1/fixtures/id_001.yaml +63 -0
  40. agentspm-0.1.1/policies/v0.1/fixtures/id_002.yaml +56 -0
  41. agentspm-0.1.1/policies/v0.1/fixtures/id_003.yaml +39 -0
  42. agentspm-0.1.1/policies/v0.1/fixtures/id_004.yaml +41 -0
  43. agentspm-0.1.1/policies/v0.1/fixtures/inv_001.yaml +42 -0
  44. agentspm-0.1.1/policies/v0.1/fixtures/inv_002.yaml +47 -0
  45. agentspm-0.1.1/policies/v0.1/fixtures/inv_003.yaml +42 -0
  46. agentspm-0.1.1/policies/v0.1/fixtures/sc_001.yaml +43 -0
  47. agentspm-0.1.1/policies/v0.1/fixtures/shared.yaml +15 -0
  48. agentspm-0.1.1/policies/v0.1/rego/data_001.rego +106 -0
  49. agentspm-0.1.1/policies/v0.1/rego/data_002.rego +141 -0
  50. agentspm-0.1.1/policies/v0.1/rego/data_003.rego +75 -0
  51. agentspm-0.1.1/policies/v0.1/rego/egress_001.rego +102 -0
  52. agentspm-0.1.1/policies/v0.1/rego/egress_002.rego +69 -0
  53. agentspm-0.1.1/policies/v0.1/rego/id_001.rego +79 -0
  54. agentspm-0.1.1/policies/v0.1/rego/id_002.rego +88 -0
  55. agentspm-0.1.1/policies/v0.1/rego/id_003.rego +67 -0
  56. agentspm-0.1.1/policies/v0.1/rego/id_004.rego +65 -0
  57. agentspm-0.1.1/policies/v0.1/rego/inv_001.rego +68 -0
  58. agentspm-0.1.1/policies/v0.1/rego/inv_002.rego +64 -0
  59. agentspm-0.1.1/policies/v0.1/rego/inv_003.rego +77 -0
  60. agentspm-0.1.1/policies/v0.1/rego/lib.rego +105 -0
  61. agentspm-0.1.1/policies/v0.1/rego/main.rego +21 -0
  62. agentspm-0.1.1/policies/v0.1/rego/sc_001.rego +86 -0
  63. agentspm-0.1.1/policies/v0.1/tests/data_001_test.rego +44 -0
  64. agentspm-0.1.1/policies/v0.1/tests/data_002_test.rego +44 -0
  65. agentspm-0.1.1/policies/v0.1/tests/data_003_test.rego +44 -0
  66. agentspm-0.1.1/policies/v0.1/tests/egress_001_test.rego +44 -0
  67. agentspm-0.1.1/policies/v0.1/tests/egress_002_test.rego +47 -0
  68. agentspm-0.1.1/policies/v0.1/tests/id_001_test.rego +44 -0
  69. agentspm-0.1.1/policies/v0.1/tests/id_002_test.rego +44 -0
  70. agentspm-0.1.1/policies/v0.1/tests/id_003_test.rego +44 -0
  71. agentspm-0.1.1/policies/v0.1/tests/id_004_test.rego +44 -0
  72. agentspm-0.1.1/policies/v0.1/tests/inv_001_test.rego +44 -0
  73. agentspm-0.1.1/policies/v0.1/tests/inv_002_test.rego +44 -0
  74. agentspm-0.1.1/policies/v0.1/tests/inv_003_test.rego +44 -0
  75. agentspm-0.1.1/policies/v0.1/tests/lib_test.rego +89 -0
  76. agentspm-0.1.1/policies/v0.1/tests/sc_001_test.rego +47 -0
  77. agentspm-0.1.1/pyproject.toml +92 -0
  78. agentspm-0.1.1/schema/examples/agent-ai-act.yaml +40 -0
  79. agentspm-0.1.1/schema/examples/agent.yaml +45 -0
  80. agentspm-0.1.1/schema/examples/identity.yaml +19 -0
  81. agentspm-0.1.1/schema/examples/model.yaml +16 -0
  82. agentspm-0.1.1/schema/examples/tool-mcp.yaml +28 -0
  83. agentspm-0.1.1/schema/v0.1/agentspm.schema.json +765 -0
  84. agentspm-0.1.1/schema/v0.1/findings.schema.json +57 -0
  85. agentspm-0.1.1/scripts/anchor_lint.py +178 -0
  86. agentspm-0.1.1/scripts/build_release.sh +41 -0
  87. agentspm-0.1.1/scripts/default_pass_lint.py +41 -0
  88. agentspm-0.1.1/scripts/generate_controls_md.py +164 -0
  89. agentspm-0.1.1/scripts/identifier_scan.py +267 -0
  90. agentspm-0.1.1/scripts/language_lint.py +154 -0
  91. agentspm-0.1.1/scripts/probe_live_gateway.py +132 -0
  92. agentspm-0.1.1/scripts/setup-hooks.sh +9 -0
  93. agentspm-0.1.1/scripts/unknown_if_lint.py +60 -0
  94. agentspm-0.1.1/scripts/validate_examples.py +46 -0
  95. agentspm-0.1.1/src/agentspm/__init__.py +6 -0
  96. agentspm-0.1.1/src/agentspm/canonical.py +63 -0
  97. agentspm-0.1.1/src/agentspm/cli.py +624 -0
  98. agentspm-0.1.1/src/agentspm/collectors/__init__.py +173 -0
  99. agentspm-0.1.1/src/agentspm/collectors/base.py +402 -0
  100. agentspm-0.1.1/src/agentspm/collectors/entra.py +603 -0
  101. agentspm-0.1.1/src/agentspm/collectors/gateway.py +357 -0
  102. agentspm-0.1.1/src/agentspm/collectors/gitrepo.py +465 -0
  103. agentspm-0.1.1/src/agentspm/collectors/mock_gateway.py +91 -0
  104. agentspm-0.1.1/src/agentspm/doctor.py +390 -0
  105. agentspm-0.1.1/src/agentspm/evaluate.py +288 -0
  106. agentspm-0.1.1/src/agentspm/graph/__init__.py +31 -0
  107. agentspm-0.1.1/src/agentspm/graph/derive.py +259 -0
  108. agentspm-0.1.1/src/agentspm/graph/merge.py +495 -0
  109. agentspm-0.1.1/src/agentspm/graph/store.py +353 -0
  110. agentspm-0.1.1/src/agentspm/gwverify.py +384 -0
  111. agentspm-0.1.1/src/agentspm/mockgw/__init__.py +28 -0
  112. agentspm-0.1.1/src/agentspm/mockgw/fixtures.py +229 -0
  113. agentspm-0.1.1/src/agentspm/mockgw/keys.py +101 -0
  114. agentspm-0.1.1/src/agentspm/mockgw/server.py +188 -0
  115. agentspm-0.1.1/src/agentspm/models.py +274 -0
  116. agentspm-0.1.1/src/agentspm/purge.py +196 -0
  117. agentspm-0.1.1/src/agentspm/py.typed +0 -0
  118. agentspm-0.1.1/src/agentspm/report/__init__.py +224 -0
  119. agentspm-0.1.1/src/agentspm/report/json.py +21 -0
  120. agentspm-0.1.1/src/agentspm/report/md.py +199 -0
  121. agentspm-0.1.1/src/agentspm/report/redact.py +278 -0
  122. agentspm-0.1.1/src/agentspm/report/sarif.py +151 -0
  123. agentspm-0.1.1/src/agentspm/validate.py +184 -0
  124. agentspm-0.1.1/tests/__init__.py +0 -0
  125. agentspm-0.1.1/tests/cli/__init__.py +0 -0
  126. agentspm-0.1.1/tests/cli/test_cli.py +511 -0
  127. agentspm-0.1.1/tests/collectors/__init__.py +0 -0
  128. agentspm-0.1.1/tests/collectors/conftest.py +91 -0
  129. agentspm-0.1.1/tests/collectors/test_base.py +153 -0
  130. agentspm-0.1.1/tests/collectors/test_entra.py +423 -0
  131. agentspm-0.1.1/tests/collectors/test_gateway_collector.py +302 -0
  132. agentspm-0.1.1/tests/collectors/test_gitrepo.py +276 -0
  133. agentspm-0.1.1/tests/collectors/test_runner.py +171 -0
  134. agentspm-0.1.1/tests/conftest.py +45 -0
  135. agentspm-0.1.1/tests/contract/__init__.py +1 -0
  136. agentspm-0.1.1/tests/contract/conftest.py +87 -0
  137. agentspm-0.1.1/tests/contract/openapi_adapter.py +81 -0
  138. agentspm-0.1.1/tests/contract/test_agents_contract.py +234 -0
  139. agentspm-0.1.1/tests/contract/test_auth_branches.py +73 -0
  140. agentspm-0.1.1/tests/contract/test_events_contract.py +90 -0
  141. agentspm-0.1.1/tests/contract/test_inventory_contract.py +97 -0
  142. agentspm-0.1.1/tests/e2e/__init__.py +0 -0
  143. agentspm-0.1.1/tests/e2e/test_quickstart.py +148 -0
  144. agentspm-0.1.1/tests/graph/__init__.py +1 -0
  145. agentspm-0.1.1/tests/graph/conftest.py +92 -0
  146. agentspm-0.1.1/tests/graph/test_derive.py +186 -0
  147. agentspm-0.1.1/tests/graph/test_graph_hash.py +80 -0
  148. agentspm-0.1.1/tests/graph/test_merge.py +250 -0
  149. agentspm-0.1.1/tests/graph/test_store.py +84 -0
  150. agentspm-0.1.1/tests/report/__init__.py +0 -0
  151. agentspm-0.1.1/tests/report/test_redact.py +159 -0
  152. agentspm-0.1.1/tests/report/test_render.py +222 -0
  153. agentspm-0.1.1/tests/test_anchor_lint.py +104 -0
  154. agentspm-0.1.1/tests/test_external_ref.py +66 -0
  155. agentspm-0.1.1/tests/test_gwverify.py +88 -0
  156. agentspm-0.1.1/tests/test_identifier_scan.py +82 -0
  157. agentspm-0.1.1/tests/test_language_lint.py +74 -0
  158. agentspm-0.1.1/tests/test_models.py +105 -0
  159. agentspm-0.1.1/tests/test_opa_pin_consistency.py +56 -0
  160. agentspm-0.1.1/tests/test_policy_findings.py +160 -0
  161. agentspm-0.1.1/tests/test_policy_pack.py +178 -0
  162. agentspm-0.1.1/tests/test_purge.py +188 -0
  163. agentspm-0.1.1/tests/test_schema.py +133 -0
  164. agentspm-0.1.1/uv.lock +2439 -0
@@ -0,0 +1,74 @@
1
+ # agentspm environment template (FR-D5: tenant-agnostic by design).
2
+ # Copy to .env and fill in values for YOUR tenant. The .env file is gitignored
3
+ # and must never be committed. The values below are DOCUMENTED SYNTHETIC
4
+ # placeholders (see scripts/identifier_scan.py) — never put real identifiers
5
+ # into tracked files.
6
+
7
+ # --- Entra ID collector (first IdP collector implementation) -----------------
8
+ # Create a DEDICATED app registration for the collector (never reuse another
9
+ # app), with READ-ONLY application permissions only:
10
+ # - Application.Read.All
11
+ # - DelegatedPermissionGrant.Read.All (ONLY if consent grants are in scope)
12
+ # - Directory.Read.All is FORBIDDEN (would expose users/groups/devices)
13
+ # - no write scopes; tenant-wide admin consent documented
14
+ # Credential: certificate preferred over client secret; expiry <= 180 days.
15
+ AGENTSPM_TENANT_ID=11111111-1111-4111-8111-111111111111
16
+ AGENTSPM_CLIENT_ID=22222222-2222-4222-8222-222222222222
17
+
18
+ # Path to the client certificate used for OIDC client-credentials auth.
19
+ # Keep key material outside the repository; *.pem/*.key files are commit-blocked.
20
+ AGENTSPM_CLIENT_CERT_PATH=/path/to/collector-client-cert.pem
21
+
22
+ # Client SECRET fallback (used only when no certificate path is configured;
23
+ # the collector logs a WARN — certificate is the SEC-D3 preference):
24
+ #AGENTSPM_CLIENT_SECRET=
25
+
26
+ # URN organization segment used in generated asset ids
27
+ # (agentspm:<type>:<org>:<name>); lowercase letters/digits/hyphens.
28
+ AGENTSPM_ORG=contoso
29
+
30
+ # --- gitrepo collector --------------------------------------------------------
31
+ # READ-ONLY repository token: GitHub fine-grained/App token (contents: read)
32
+ # or a GitLab token with the read_api scope; expiry <= 90 days.
33
+ #AGENTSPM_GITREPO_TOKEN=
34
+
35
+ # --- gateway collector --------------------------------------------------------
36
+ # Client secret for the OIDC client-credentials flow (posture-reader scope).
37
+ #AGENTSPM_GATEWAY_CLIENT_SECRET=
38
+
39
+ # --- shared HTTP client (all collectors) --------------------------------------
40
+ # Per-request timeout in seconds and the retry budget (429/5xx + backoff;
41
+ # Retry-After is honored on throttling responses).
42
+ #AGENTSPM_HTTP_TIMEOUT=30
43
+ #AGENTSPM_HTTP_MAX_ATTEMPTS=4
44
+ # Corporate CA bundle and egress proxy (standard HTTPS_PROXY/SSL_CERT_FILE
45
+ # are honored as well via trust_env).
46
+ #AGENTSPM_CA_BUNDLE=/etc/pki/tls/certs/corp-bundle.pem
47
+ #AGENTSPM_HTTPS_PROXY=http://proxy.example.com:8080
48
+
49
+ # --- Evidence store -----------------------------------------------------------
50
+ # Local directory for raw snapshots and full (non-redacted) reports.
51
+ # Keep it on an encrypted volume; it never leaves the host and is gitignored.
52
+ AGENTSPM_EVIDENCE_STORE=./evidence-store
53
+
54
+ # --- CLI pipeline ---------------------------------------------------------------
55
+ # SQLite graph database written by `agentspm scan` (default: ./agentspm.sqlite).
56
+ #AGENTSPM_DB=agentspm.sqlite
57
+ # Deployment config path (default: ./agentspm.yaml, then the example config).
58
+ #AGENTSPM_CONFIG=deploy/agentspm.example.yaml
59
+
60
+ # OPA binary + sha256 pin: `agentspm evaluate` ALWAYS verifies the binary
61
+ # against this pin and refuses to run an unpinned engine (INV-07).
62
+ #AGENTSPM_OPA_PATH=/usr/local/bin/opa
63
+ #AGENTSPM_OPA_SHA256=<sha256 of the pinned opa binary>
64
+ # Policy pack override (default: ./policies/v0.1).
65
+ #AGENTSPM_POLICIES_DIR=policies/v0.1
66
+
67
+ # Pseudonymization key for redacted reports (default location:
68
+ # <evidence store>/pseudonymization.key; generated on first use, mode 0600).
69
+ # The alias->value mapping stays next to the key — evidence store only.
70
+ #AGENTSPM_PSEUDO_KEY_PATH=./evidence-store/pseudonymization.key
71
+
72
+ # age binary for `agentspm export` (ASSUM-08: system binary, no Python age
73
+ # dependency; default /usr/bin/age, then PATH).
74
+ #AGENTSPM_AGE_PATH=/usr/bin/age
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env bash
2
+ # agentspm pre-commit hook (SEC-D5) — active from the FIRST commit.
3
+ # BLOCK: sensitive file names, secret patterns, real identifiers (NFR-D1).
4
+ # WARN: forbidden language patterns (pre-push and CI BLOCK them).
5
+ # Install: scripts/setup-hooks.sh (git config core.hooksPath .githooks)
6
+ #
7
+ # Note: the secret regexes are written in a self-non-matching form (e.g. a
8
+ # character class instead of the final literal character) so that committing
9
+ # this hook and the scan config does not trip the scan itself.
10
+ set -euo pipefail
11
+
12
+ mapfile -t STAGED < <(git diff --cached --name-only --diff-filter=ACM)
13
+ if [ "${#STAGED[@]}" -eq 0 ]; then
14
+ exit 0
15
+ fi
16
+
17
+ fail=0
18
+
19
+ # --- 1. sensitive file names (never commit these) ---------------------------
20
+ for f in "${STAGED[@]}"; do
21
+ base=$(basename "$f")
22
+ case "$base" in
23
+ .env|.env.local|.env.production)
24
+ echo "BLOCK: sensitive file staged: $f (use .env.example)"; fail=1 ;;
25
+ *.pem|*.key|id_rsa|id_ed25519)
26
+ echo "BLOCK: key material staged: $f"; fail=1 ;;
27
+ esac
28
+ done
29
+
30
+ # --- 2. secret content scan (gitleaks-style) --------------------------------
31
+ SECRET_PATTERNS=(
32
+ 'AGE-SECRET-KEY-[0-9A-Z]'
33
+ 'BEGIN[[:space:]]+[A-Z ]*PRIVATE[[:space:]]+KEY'
34
+ 'gw-[A-Za-z0-9]{8,}'
35
+ 'AKIA[0-9A-Z]{16}'
36
+ 'ssh-rsa[[:space:]]+AAAA'
37
+ "(password|secret|api[_-]?key|token)[[:space:]]*=[[:space:]]*[\"'][^\"']{8,}"
38
+ )
39
+ for f in "${STAGED[@]}"; do
40
+ # Allowlist (Git Hook Strategy): fixtures carrying an explicit synthetic marker.
41
+ if [[ "$f" == tests/fixtures/* ]] && git show ":$f" 2>/dev/null | grep -q 'synthetic: true'; then
42
+ continue
43
+ fi
44
+ content=$(git show ":$f" 2>/dev/null) || continue
45
+ for pat in "${SECRET_PATTERNS[@]}"; do
46
+ if hits=$(printf '%s\n' "$content" | grep -I -n -E "$pat"); then
47
+ echo "BLOCK: secret-pattern match in $f:"
48
+ echo "$hits" | head -5
49
+ fail=1
50
+ fi
51
+ done
52
+ done
53
+
54
+ # --- 3. identifier scan on staged content (NFR-D1) — BLOCK ------------------
55
+ if ! python3 scripts/identifier_scan.py --staged; then
56
+ echo "BLOCK: real identifiers must never enter git history (NFR-D1)."
57
+ fail=1
58
+ fi
59
+
60
+ # --- 4. language lint — WARN at commit (pre-push/CI BLOCK) ------------------
61
+ if ! python3 scripts/language_lint.py --staged; then
62
+ echo "WARN: forbidden language patterns in staged files (pre-push will BLOCK)."
63
+ fi
64
+
65
+ if [ "$fail" -ne 0 ]; then
66
+ echo "pre-commit: BLOCKED. Fix the findings above; never bypass with --no-verify."
67
+ fi
68
+ exit "$fail"
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bash
2
+ # agentspm pre-push hook (SEC-D5).
3
+ # BLOCK: forbidden language patterns, unignored dependency vulnerabilities
4
+ # (CRITICAL/HIGH policy — any unignored pip-audit finding blocks),
5
+ # policy lints (default-pass, unknown_if, anchors, CONTROLS.md drift)
6
+ # and the opa test suite when an opa binary is available.
7
+ set -euo pipefail
8
+
9
+ echo "[pre-push] language lint (BLOCK)"
10
+ python3 scripts/language_lint.py
11
+
12
+ echo "[pre-push] policy lints (BLOCK)"
13
+ python3 scripts/default_pass_lint.py
14
+ if [ -d policies ] && command -v uv >/dev/null 2>&1; then
15
+ uv run python scripts/unknown_if_lint.py
16
+ uv run python scripts/anchor_lint.py
17
+ uv run python scripts/generate_controls_md.py --check
18
+ fi
19
+
20
+ echo "[pre-push] opa test (policy pack, BLOCK when opa is available)"
21
+ OPA_BIN="${OPA_BIN:-$HOME/.local/bin/opa}"
22
+ if ! command -v "$OPA_BIN" >/dev/null 2>&1; then
23
+ OPA_BIN=$(command -v opa || true)
24
+ fi
25
+ if [ -n "$OPA_BIN" ] && [ -d policies/v0.1 ]; then
26
+ "$OPA_BIN" check --strict --capabilities policies/v0.1/capabilities.json \
27
+ policies/v0.1/rego/ policies/v0.1/tests/
28
+ "$OPA_BIN" test policies/v0.1/ --capabilities policies/v0.1/capabilities.json
29
+ else
30
+ echo "WARN: opa not available; policy tests skipped locally (CI enforces them)."
31
+ fi
32
+
33
+ echo "[pre-push] dependency audit (pip-audit — unignored finding = BLOCK)"
34
+ if command -v uv >/dev/null 2>&1; then
35
+ req=$(mktemp)
36
+ trap 'rm -f "$req"' EXIT
37
+ # Export nese cely uzamceny uzaver s hashi, takze `--no-deps` nize je zamer,
38
+ # ne zjednoduseni (detail v komentari u stejneho kroku v ci.yml).
39
+ uv export --locked --no-emit-project --format requirements.txt --quiet -o "$req"
40
+ # Allowlisted findings = documented false positives WITH expiry, kept in
41
+ # .vuln-scan-config.yaml (pre_push.dependency_audit.ignore).
42
+ ignore_args=()
43
+ while IFS= read -r vid; do
44
+ ignore_args+=(--ignore-vuln "$vid")
45
+ done < <(python3 - <<'PYEOF'
46
+ import re
47
+ from pathlib import Path
48
+
49
+ cfg = Path(".vuln-scan-config.yaml")
50
+ if cfg.exists():
51
+ for m in re.finditer(r"^\s*-\s*id:\s*([A-Za-z0-9-]+)", cfg.read_text(), re.M):
52
+ print(m.group(1))
53
+ PYEOF
54
+ )
55
+ if ! uv run pip-audit -r "$req" --no-deps --disable-pip "${ignore_args[@]}"; then
56
+ echo "BLOCK: pip-audit reported unignored vulnerabilities."
57
+ exit 1
58
+ fi
59
+ else
60
+ echo "WARN: uv not available; dependency audit skipped locally (CI enforces it)."
61
+ fi
62
+
63
+ echo "[pre-push] OK"
@@ -0,0 +1,194 @@
1
+ # agentspm CI — every gate is enforcing: NO continue-on-error anywhere.
2
+ # Runs without any gateway checkout or network dependency on it (INV-09 / AK-13).
3
+ name: ci
4
+
5
+ on:
6
+ push:
7
+ pull_request:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ env:
13
+ # 1.21.2 -> 1.21.4 dne 27. 10. 2026: trivy nasel v pinnute binarce HIGH
14
+ # CVE-2026-111943 (crypto/x509 v go1.27.2, MITM pres nedouveryhodne
15
+ # intermediate certifikaty). 1.21.4 je postavena go1.27.3, tedy opravenou.
16
+ # Pin se nesnizuje — nizsi verze znamena vratit tu zranitelnost.
17
+ OPA_VERSION: "1.21.4"
18
+ OPA_SHA256: "2ab571f367f293bb19523b76f92f1945bbf245003392c7f392903f95c2b9b630"
19
+
20
+ jobs:
21
+ lint:
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: astral-sh/setup-uv@v5
26
+ - run: uv sync --locked
27
+ - name: ruff
28
+ run: uv run ruff check .
29
+ - name: mypy (strict)
30
+ run: uv run mypy src/
31
+
32
+ security:
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ - uses: astral-sh/setup-uv@v5
37
+ - run: uv sync --locked
38
+ - name: bandit (SAST, enforcing — MEDIUM+ blocks per .vuln-scan-config.yaml)
39
+ run: uv run bandit -c pyproject.toml -r src scripts --severity-level medium
40
+ - name: pip-audit (enforcing — exit code gates the job)
41
+ run: |
42
+ # `uv export` vypisuje CELOU uzamcenou tranzitivni mnozinu VCETNE hashu
43
+ # (dnes 1413 radku `--hash=`), takze `--no-deps` nize je spravne: soubor
44
+ # uz je kompletni uzaver a pip-audit nema co doresovat. pip-audit k tomu
45
+ # tiskne obecnou radu "users are encouraged to fully hash your pinned
46
+ # dependencies" -- ta se NA TENHLE pripad nevztahuje; neopravovat to
47
+ # odstranenim `--no-deps`, tim by se audit rozsiril o nezamcene verze.
48
+ uv export --locked --no-emit-project --format requirements.txt -o requirements-audit.txt
49
+ uv run pip-audit -r requirements-audit.txt --no-deps --disable-pip
50
+
51
+ test:
52
+ runs-on: ubuntu-latest
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+ - uses: astral-sh/setup-uv@v5
56
+ - run: uv sync --locked
57
+ - name: download OPA (pinned; policy findings tests shell out to it)
58
+ run: |
59
+ curl -sSfL -o /tmp/opa "https://openpolicyagent.org/downloads/v${OPA_VERSION}/opa_linux_amd64_static"
60
+ echo "${OPA_SHA256} /tmp/opa" | sha256sum --check
61
+ chmod +x /tmp/opa
62
+ - name: pytest + coverage (OPA_BIN set so no policy test skips)
63
+ run: uv run pytest --cov=agentspm --cov-report=term-missing
64
+ env:
65
+ OPA_BIN: /tmp/opa
66
+
67
+ schema-validate:
68
+ runs-on: ubuntu-latest
69
+ steps:
70
+ - uses: actions/checkout@v4
71
+ - uses: astral-sh/setup-uv@v5
72
+ - run: uv sync --locked
73
+ - name: validate schema examples against the v0.1 schema
74
+ run: uv run python scripts/validate_examples.py
75
+
76
+ opa:
77
+ runs-on: ubuntu-latest
78
+ steps:
79
+ - uses: actions/checkout@v4
80
+ - name: download OPA (pinned version + sha256)
81
+ run: |
82
+ curl -sSfL -o opa "https://openpolicyagent.org/downloads/v${OPA_VERSION}/opa_linux_amd64_static"
83
+ echo "${OPA_SHA256} opa" | sha256sum --check
84
+ chmod +x opa
85
+ - name: opa check (strict, pinned capabilities — no network builtins)
86
+ run: >-
87
+ ./opa check --strict
88
+ --capabilities policies/v0.1/capabilities.json
89
+ policies/v0.1/rego/ policies/v0.1/tests/
90
+ - name: opa test (13 controls x 3 tri-state fixtures + adversarial)
91
+ run: ./opa test policies/v0.1/ -v --capabilities policies/v0.1/capabilities.json
92
+
93
+ # Offline/proxy install test (B10): the wheel must install into a clean venv
94
+ # with NO index access, from a wheelhouse prepared ahead of time — the
95
+ # bastion/air-gapped deployment path (docs/ARCHITECTURE.md).
96
+ offline-install:
97
+ runs-on: ubuntu-latest
98
+ steps:
99
+ - uses: actions/checkout@v4
100
+ - uses: astral-sh/setup-uv@v5
101
+ - name: build the wheel
102
+ run: uv build
103
+ - name: prepare the offline wheelhouse (locked runtime deps, wheels only)
104
+ run: |
105
+ uv export --locked --no-emit-project --no-dev \
106
+ --format requirements.txt -o /tmp/requirements-runtime.txt
107
+ python3 -m pip download --only-binary :all: \
108
+ -r /tmp/requirements-runtime.txt -d /tmp/wheelhouse
109
+ - name: install into a clean venv with --no-index + smoke test
110
+ run: |
111
+ python3 -m venv /tmp/venv-offline
112
+ /tmp/venv-offline/bin/pip install --no-index \
113
+ --find-links /tmp/wheelhouse --find-links dist/ dist/agentspm-*.whl
114
+ /tmp/venv-offline/bin/agentspm --help
115
+
116
+ identifier-scan:
117
+ runs-on: ubuntu-latest
118
+ steps:
119
+ - uses: actions/checkout@v4
120
+ with:
121
+ fetch-depth: 0 # full history for the --history scan
122
+ - name: identifier scan — worktree (NFR-D1)
123
+ run: python3 scripts/identifier_scan.py
124
+ - name: identifier scan — full git history (all refs)
125
+ run: python3 scripts/identifier_scan.py --history
126
+
127
+ secret-scan:
128
+ # P1 zadal "gitleaks/trufflehog + identifier_scan.py --history", ale v CI byl
129
+ # jen ten druhy. Sken tajemstvi tedy existoval jako VETA v docs/RELEASE-GATE.md,
130
+ # ne jako brana -- a veta nikdy nespadne. Dolozeno merenim 18. 10. 2026: gitleaks
131
+ # nad celou historii hlasi 0 nalezu, takze tenhle job startuje ze zelene a od
132
+ # teto chvile to hlida, misto aby to nekdo rucne pripominal pred publikaci.
133
+ runs-on: ubuntu-latest
134
+ steps:
135
+ - uses: actions/checkout@v4
136
+ with:
137
+ fetch-depth: 0 # cela historie, jinak sken kryje jen posledni commit
138
+ - name: gitleaks — full history (P1)
139
+ # Pripnuto na SHA, ne na tag: tag je pohybliva reference, a projekt, ktery
140
+ # pinuje OPA na sha256 a podepisuje artefakty, nema dovoz CI akce resit
141
+ # slabeji. SHA = tag v2.3.9.
142
+ uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7
143
+ env:
144
+ GITLEAKS_ENABLE_UPLOAD_ARTIFACT: "false"
145
+ GITLEAKS_ENABLE_SUMMARY: "true"
146
+
147
+ image-scan:
148
+ # P5 zada "pip-audit/trivy s exit-code != 0". pip-audit v jobu `security` je
149
+ # enforcing, ale image z Containerfile se do dneska nikde nestavel ani neskenoval.
150
+ # Proc to nestaci: v sesterskem projektu hlasil pip-audit 0 nalezu, zatimco sken
151
+ # IMAGE nasel 4 HIGH ve knihovnach vendorovanych uvnitr pipu. Audit deklarovanych
152
+ # zavislosti a sken vysledneho image jsou dve ruzne otazky.
153
+ runs-on: ubuntu-latest
154
+ steps:
155
+ - uses: actions/checkout@v4
156
+ - name: build image (Containerfile)
157
+ run: docker build -f Containerfile -t agentspm:ci .
158
+ - name: trivy — image, enforcing
159
+ # SHA = tag v0.36.0. Prvni pokus mel `@0.28.0`, coz NEEXISTUJE a job
160
+ # spadl na "unable to resolve action" -- tedy brana, ktera nemerila nic,
161
+ # jen hlasila cervenou ze spatneho duvodu. Verze akce patri overit, ne
162
+ # odhadnout.
163
+ uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25
164
+ with:
165
+ image-ref: agentspm:ci
166
+ severity: HIGH,CRITICAL
167
+ ignore-unfixed: true
168
+ exit-code: "1" # NIKDY 0: brana, ktera nemuze selhat, nehlida nic
169
+ format: table
170
+ # Explicitni cesta misto spolehani na default: kazda vyjimka MUSI
171
+ # byt v reviewovanem .trivyignore s duvodem a podminkou odstraneni.
172
+ trivyignores: .trivyignore
173
+
174
+ language-lint:
175
+ runs-on: ubuntu-latest
176
+ steps:
177
+ - uses: actions/checkout@v4
178
+ - name: language lint (BLOCK outside docs/decisions/)
179
+ run: python3 scripts/language_lint.py
180
+
181
+ policy-lints:
182
+ runs-on: ubuntu-latest
183
+ steps:
184
+ - uses: actions/checkout@v4
185
+ - uses: astral-sh/setup-uv@v5
186
+ - run: uv sync --locked
187
+ - name: default-pass lint (fail-open ban in rego)
188
+ run: python3 scripts/default_pass_lint.py
189
+ - name: unknown_if lint (every control declares unknown_if)
190
+ run: uv run python scripts/unknown_if_lint.py
191
+ - name: anchor lint (forbidden/required regulatory anchors per 05c)
192
+ run: uv run python scripts/anchor_lint.py
193
+ - name: CONTROLS.md is current (regenerate + diff = 0)
194
+ run: uv run python scripts/generate_controls_md.py --check
@@ -0,0 +1,150 @@
1
+ # agentspm release (B10) — MANUAL ONLY (workflow_dispatch).
2
+ #
3
+ # Produces: wheel + sdist, CycloneDX SBOM, SHA256SUMS, KEYLESS cosign
4
+ # signatures (Sigstore bundles), and a GitHub Release.
5
+ #
6
+ # Signing is KEYLESS (P4 decision, 2026-11-11, maintainer): the job's GitHub
7
+ # OIDC identity obtains a short-lived certificate from Fulcio and the
8
+ # signature is recorded in the public Rekor transparency log. Consequences,
9
+ # understood and accepted (docs/RELEASE-GATE.md P4):
10
+ # * there is NO private signing key — nothing to provision, leak or rotate;
11
+ # * every release is publicly visible in Rekor (repo + workflow identity);
12
+ # that is why the FIRST keyless run happens at publication time, not in
13
+ # the private phase (the earlier private-phase design was key-based with
14
+ # --tlog-upload=false for exactly this reason);
15
+ # * verification pins the WORKFLOW identity, not a public key:
16
+ # cosign verify-blob \
17
+ # --bundle <artifact>.sigstore.json \
18
+ # --certificate-identity-regexp \
19
+ # '^https://github.com/vojtahampl/agentspm/\.github/workflows/release\.yml@' \
20
+ # --certificate-oidc-issuer https://token.actions.githubusercontent.com \
21
+ # <artifact>
22
+ # The .sigstore.json bundle carries the certificate + Rekor proof, so the
23
+ # verify step works OFFLINE (air-gapped consumers verify the downloaded
24
+ # bundle; SHA256SUMS remains as the key-free fallback).
25
+ name: release
26
+
27
+ on:
28
+ workflow_dispatch:
29
+ inputs:
30
+ tag:
31
+ description: "Release tag (e.g. v0.1.0 or v0.1.0-dev.1)"
32
+ required: true
33
+ type: string
34
+
35
+ permissions:
36
+ contents: write # create the GitHub Release
37
+ id-token: write # keyless signing: the OIDC token IS the signing identity
38
+
39
+ jobs:
40
+ release:
41
+ runs-on: ubuntu-latest
42
+ environment: release # branch-restricted; keeps the manual gate (T-15)
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+
46
+ - name: refuse to release from a non-default branch
47
+ run: |
48
+ if [ "${GITHUB_REF_NAME}" != "main" ]; then
49
+ echo "releases are cut from main only (got ${GITHUB_REF_NAME})" >&2
50
+ exit 1
51
+ fi
52
+
53
+ - name: guard — tag must equal pyproject version (RR-10)
54
+ env:
55
+ TAG: ${{ inputs.tag }}
56
+ run: |
57
+ python3 - <<'EOF'
58
+ import os, pathlib, re, sys, tomllib
59
+
60
+ tag = os.environ["TAG"]
61
+ project = tomllib.loads(pathlib.Path("pyproject.toml").read_text())
62
+ version = project["project"]["version"]
63
+
64
+ # Tag convention vX.Y.Z[-dev.N]; pyproject is PEP 440 (X.Y.Z[.devN]).
65
+ normalized_tag = tag.removeprefix("v")
66
+ normalized_tag = re.sub(r"-dev\.?(\d+)$", r".dev\1", normalized_tag)
67
+ # bare "-dev" tags normalize to ".dev0", matching "0.1.0.dev0"
68
+ normalized_tag = re.sub(r"-dev$", r".dev0", normalized_tag)
69
+
70
+ if normalized_tag != version:
71
+ sys.exit(
72
+ f"tag {tag!r} (normalized {normalized_tag!r}) does not match "
73
+ f"pyproject version {version!r} — bump pyproject.toml first; "
74
+ "one version, everywhere (RR-10)"
75
+ )
76
+ print(f"tag {tag} == pyproject {version}: OK")
77
+ EOF
78
+
79
+ - name: guard — CHANGELOG has an entry for this version
80
+ env:
81
+ TAG: ${{ inputs.tag }}
82
+ run: |
83
+ base="${TAG#v}"; base="${base%%-dev*}"
84
+ if ! grep -q "^## \[${base}\]" CHANGELOG.md; then
85
+ echo "CHANGELOG.md has no '## [${base}]' section — write the release notes first (RR-12)" >&2
86
+ exit 1
87
+ fi
88
+ echo "CHANGELOG entry for ${base}: OK"
89
+
90
+ - uses: astral-sh/setup-uv@v5
91
+ - run: uv sync --locked
92
+
93
+ - name: build wheel + sdist + SBOM + checksums
94
+ run: scripts/build_release.sh
95
+
96
+ - name: install smoke test — the exact artifact, offline, clean venv
97
+ run: |
98
+ uv export --locked --no-emit-project --no-dev \
99
+ --format requirements.txt -o /tmp/requirements-runtime.txt
100
+ python3 -m pip download --only-binary :all: \
101
+ -r /tmp/requirements-runtime.txt -d /tmp/wheelhouse
102
+ python3 -m venv /tmp/venv-release
103
+ /tmp/venv-release/bin/pip install --no-index \
104
+ --find-links /tmp/wheelhouse --find-links dist/ dist/agentspm-*.whl
105
+ /tmp/venv-release/bin/agentspm --help
106
+
107
+ - name: install cosign
108
+ uses: sigstore/cosign-installer@v3
109
+
110
+ - name: sign artifacts (keyless, Sigstore bundle per artifact)
111
+ run: |
112
+ for artifact in dist/*.whl dist/*.tar.gz dist/*.cdx.json dist/SHA256SUMS; do
113
+ cosign sign-blob --yes \
114
+ --bundle "${artifact}.sigstore.json" \
115
+ "${artifact}"
116
+ done
117
+
118
+ - name: verify what we just signed (the gate must be able to fail)
119
+ run: |
120
+ for artifact in dist/*.whl dist/*.tar.gz dist/*.cdx.json dist/SHA256SUMS; do
121
+ cosign verify-blob \
122
+ --bundle "${artifact}.sigstore.json" \
123
+ --certificate-identity-regexp \
124
+ '^https://github.com/vojtahampl/agentspm/\.github/workflows/release\.yml@' \
125
+ --certificate-oidc-issuer https://token.actions.githubusercontent.com \
126
+ "${artifact}"
127
+ done
128
+
129
+ - name: create GitHub Release
130
+ env:
131
+ GH_TOKEN: ${{ github.token }}
132
+ TAG: ${{ inputs.tag }}
133
+ run: |
134
+ gh release create "${TAG}" dist/* \
135
+ --target "${GITHUB_SHA}" \
136
+ --title "agentspm ${TAG}" \
137
+ --notes "wheel + sdist, CycloneDX SBOM, SHA256SUMS, keyless cosign signatures (.sigstore.json bundles — verification: docs/ARCHITECTURE.md, Verifying release artifacts). Generated over synthetic fixtures; see docs/LANGUAGE.md for the claims rule. Also on PyPI: pip install agentspm"
138
+
139
+ - name: publish to PyPI (Trusted Publishing — no token anywhere)
140
+ # The pending publisher on pypi.org is bound to exactly this workflow
141
+ # file + the `release` environment; the job's OIDC token is the whole
142
+ # credential. Only the installable artifacts go up — PyPI has no
143
+ # concept of SBOM/checksum sidecars, those live on the GitHub Release.
144
+ run: |
145
+ mkdir -p dist-pypi
146
+ cp dist/*.whl dist/*.tar.gz dist-pypi/
147
+ ls -l dist-pypi/
148
+ - uses: pypa/gh-action-pypi-publish@release/v1
149
+ with:
150
+ packages-dir: dist-pypi/
@@ -0,0 +1,32 @@
1
+ # local env & secrets — never commit
2
+ .env
3
+ .env.*
4
+ !.env.example
5
+
6
+ # evidence store (raw snapshots, full reports) — never commit
7
+ evidence-store/
8
+
9
+ # python
10
+ __pycache__/
11
+ *.pyc
12
+ .venv/
13
+ dist/
14
+ build/
15
+ *.egg-info/
16
+
17
+ # tooling caches & local reports
18
+ .pytest_cache/
19
+ .mypy_cache/
20
+ .ruff_cache/
21
+ .coverage
22
+ coverage.xml
23
+ .vuln-scan-reports/
24
+ requirements-audit.txt
25
+
26
+ # local databases (the SQLite store is an evidence artifact, not source)
27
+ *.sqlite
28
+ *.sqlite3
29
+ *.db
30
+
31
+ # scan by-products (freshness marker written next to the database)
32
+ *.freshness.json
@@ -0,0 +1,16 @@
1
+ # Reviewed, TEMPORARY exceptions — each entry carries its reason and its
2
+ # removal condition. An entry without either does not belong here.
3
+
4
+ # CVE-2026-78661 — golang.org/x/net/html DoS, vendored inside the pinned OPA
5
+ # binary (fix is in x/net v0.62.0).
6
+ # Measured 2026-11-11: EVERY released OPA up to v1.22.3 (2026-11-10) still
7
+ # vendors x/net v0.60.0 (`strings opa | grep x/net@`), so bumping the pin
8
+ # cannot close this — there is nothing to bump to.
9
+ # Impact in THIS deployment shape: OPA runs locally over the local policy
10
+ # pack via `opa eval`; it parses no attacker-supplied HTML and listens on no
11
+ # socket, so the DoS path has no entry point (SPM-INV-04 keeps collected
12
+ # data out of policy inputs as instructions, and inputs are JSON, not HTML).
13
+ # REMOVE when an OPA release vendors golang.org/x/net >= 0.62.0 — the OPA
14
+ # pin bump then picks it up (pin lives in ci.yml + Containerfile, guarded by
15
+ # tests/test_opa_pin_consistency.py).
16
+ CVE-2026-78661
@@ -0,0 +1,55 @@
1
+ # Vulnerability & content scanning configuration — agentspm
2
+ # Derived from the Agent Network template (templates/vuln-scan-config.yaml);
3
+ # thresholds per 04-arch-security "Git Hook Strategy" (SEC-D5).
4
+ #
5
+ # Severity policy:
6
+ # pre-commit: secrets/identifiers -> BLOCK, language -> WARN
7
+ # pre-push: CRITICAL/HIGH CVE -> BLOCK, MEDIUM -> WARN, LOW -> LOG
8
+ #
9
+ # Patterns are written in a self-non-matching form (character class instead of
10
+ # the final literal) so the config itself passes the scan.
11
+
12
+ pre_commit:
13
+ block_patterns:
14
+ secrets:
15
+ - 'AGE-SECRET-KEY-[0-9A-Z]'
16
+ - 'BEGIN[[:space:]]+[A-Z ]*PRIVATE[[:space:]]+KEY'
17
+ - 'gw-[A-Za-z0-9]{8,}'
18
+ - 'AKIA[0-9A-Z]{16}'
19
+ - 'ssh-rsa[[:space:]]+AAAA'
20
+ - '(password|secret|api[_-]?key|token)[[:space:]]*=[[:space:]]*["\x27][^"\x27]{8,}'
21
+ identifier_scan: scripts/identifier_scan.py # NFR-D1, BLOCK
22
+ sensitive_files:
23
+ - '\.env$' # .env.example is allowed
24
+ - '\.pem$'
25
+ - '\.key$'
26
+ warn_patterns:
27
+ language: scripts/language_lint.py # WARN at commit, BLOCK at push/CI
28
+ allowlist:
29
+ - 'tests/fixtures/**' # only with a `synthetic: true` marker inside the file
30
+
31
+ pre_push:
32
+ dependency_audit:
33
+ tool: pip-audit
34
+ block_severity: [critical, high]
35
+ warn_severity: [medium]
36
+ ignore_severity: [low]
37
+ # pip-audit does not expose a severity for every advisory; the enforced
38
+ # policy is therefore a superset: ANY unignored finding blocks the push.
39
+ ignore: []
40
+ # ignore entries are documented false positives WITH expiry:
41
+ # - id: GHSA-xxxx-xxxx-xxxx
42
+ # expires: 2027-01-31
43
+ # reason: 'why this is a false positive here'
44
+ sast:
45
+ tool: bandit
46
+ block_severity: HIGH
47
+ warn_severity: MEDIUM
48
+ scan_paths: [src/, scripts/]
49
+ exclude_paths: [tests/]
50
+ opa_lints: # active once policies/ exists (B4)
51
+ - scripts/default_pass_lint.py # default-to-pass is forbidden
52
+ - scripts/unknown_if_lint.py # every control declares unknown_if
53
+
54
+ # Feedback from PenTest network (added by the Orchestrator after HANDOFF B)
55
+ pentest_rules: []