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.
- agentspm-0.1.1/.env.example +74 -0
- agentspm-0.1.1/.githooks/pre-commit +68 -0
- agentspm-0.1.1/.githooks/pre-push +63 -0
- agentspm-0.1.1/.github/workflows/ci.yml +194 -0
- agentspm-0.1.1/.github/workflows/release.yml +150 -0
- agentspm-0.1.1/.gitignore +32 -0
- agentspm-0.1.1/.trivyignore +16 -0
- agentspm-0.1.1/.vuln-scan-config.yaml +55 -0
- agentspm-0.1.1/CHANGELOG.md +53 -0
- agentspm-0.1.1/Containerfile +108 -0
- agentspm-0.1.1/LICENSE +202 -0
- agentspm-0.1.1/PKG-INFO +177 -0
- agentspm-0.1.1/README.md +154 -0
- agentspm-0.1.1/SECURITY.md +43 -0
- agentspm-0.1.1/contracts/CHANGELOG.md +29 -0
- agentspm-0.1.1/contracts/gateway/v0.1/openapi.yaml +342 -0
- agentspm-0.1.1/deploy/agentspm.example.yaml +114 -0
- agentspm-0.1.1/deploy/cronjob.example.yaml +80 -0
- agentspm-0.1.1/docs/ARCHITECTURE.md +166 -0
- agentspm-0.1.1/docs/CONTROLS.md +388 -0
- agentspm-0.1.1/docs/GOVERNANCE.md +40 -0
- agentspm-0.1.1/docs/INTEGRATION.md +236 -0
- agentspm-0.1.1/docs/LANGUAGE.md +66 -0
- agentspm-0.1.1/docs/LICENSE-CC-BY-4.0.txt +396 -0
- agentspm-0.1.1/docs/PRIVACY.md +216 -0
- agentspm-0.1.1/docs/RELEASE-GATE.md +78 -0
- agentspm-0.1.1/docs/THREAT-MODEL.md +50 -0
- agentspm-0.1.1/docs/adr/ADR-0001-modular-cli-pipeline.md +52 -0
- agentspm-0.1.1/docs/adr/ADR-0002-contract-first-gateway.md +38 -0
- agentspm-0.1.1/docs/adr/ADR-0003-ai-act-core-block.md +39 -0
- agentspm-0.1.1/docs/adr/ADR-0004-agent-identity-derivation.md +90 -0
- agentspm-0.1.1/policies/v0.1/capabilities.json +5098 -0
- agentspm-0.1.1/policies/v0.1/controls.yaml +615 -0
- agentspm-0.1.1/policies/v0.1/fixtures/data_001.yaml +80 -0
- agentspm-0.1.1/policies/v0.1/fixtures/data_002.yaml +120 -0
- agentspm-0.1.1/policies/v0.1/fixtures/data_003.yaml +81 -0
- agentspm-0.1.1/policies/v0.1/fixtures/egress_001.yaml +67 -0
- agentspm-0.1.1/policies/v0.1/fixtures/egress_002.yaml +46 -0
- agentspm-0.1.1/policies/v0.1/fixtures/id_001.yaml +63 -0
- agentspm-0.1.1/policies/v0.1/fixtures/id_002.yaml +56 -0
- agentspm-0.1.1/policies/v0.1/fixtures/id_003.yaml +39 -0
- agentspm-0.1.1/policies/v0.1/fixtures/id_004.yaml +41 -0
- agentspm-0.1.1/policies/v0.1/fixtures/inv_001.yaml +42 -0
- agentspm-0.1.1/policies/v0.1/fixtures/inv_002.yaml +47 -0
- agentspm-0.1.1/policies/v0.1/fixtures/inv_003.yaml +42 -0
- agentspm-0.1.1/policies/v0.1/fixtures/sc_001.yaml +43 -0
- agentspm-0.1.1/policies/v0.1/fixtures/shared.yaml +15 -0
- agentspm-0.1.1/policies/v0.1/rego/data_001.rego +106 -0
- agentspm-0.1.1/policies/v0.1/rego/data_002.rego +141 -0
- agentspm-0.1.1/policies/v0.1/rego/data_003.rego +75 -0
- agentspm-0.1.1/policies/v0.1/rego/egress_001.rego +102 -0
- agentspm-0.1.1/policies/v0.1/rego/egress_002.rego +69 -0
- agentspm-0.1.1/policies/v0.1/rego/id_001.rego +79 -0
- agentspm-0.1.1/policies/v0.1/rego/id_002.rego +88 -0
- agentspm-0.1.1/policies/v0.1/rego/id_003.rego +67 -0
- agentspm-0.1.1/policies/v0.1/rego/id_004.rego +65 -0
- agentspm-0.1.1/policies/v0.1/rego/inv_001.rego +68 -0
- agentspm-0.1.1/policies/v0.1/rego/inv_002.rego +64 -0
- agentspm-0.1.1/policies/v0.1/rego/inv_003.rego +77 -0
- agentspm-0.1.1/policies/v0.1/rego/lib.rego +105 -0
- agentspm-0.1.1/policies/v0.1/rego/main.rego +21 -0
- agentspm-0.1.1/policies/v0.1/rego/sc_001.rego +86 -0
- agentspm-0.1.1/policies/v0.1/tests/data_001_test.rego +44 -0
- agentspm-0.1.1/policies/v0.1/tests/data_002_test.rego +44 -0
- agentspm-0.1.1/policies/v0.1/tests/data_003_test.rego +44 -0
- agentspm-0.1.1/policies/v0.1/tests/egress_001_test.rego +44 -0
- agentspm-0.1.1/policies/v0.1/tests/egress_002_test.rego +47 -0
- agentspm-0.1.1/policies/v0.1/tests/id_001_test.rego +44 -0
- agentspm-0.1.1/policies/v0.1/tests/id_002_test.rego +44 -0
- agentspm-0.1.1/policies/v0.1/tests/id_003_test.rego +44 -0
- agentspm-0.1.1/policies/v0.1/tests/id_004_test.rego +44 -0
- agentspm-0.1.1/policies/v0.1/tests/inv_001_test.rego +44 -0
- agentspm-0.1.1/policies/v0.1/tests/inv_002_test.rego +44 -0
- agentspm-0.1.1/policies/v0.1/tests/inv_003_test.rego +44 -0
- agentspm-0.1.1/policies/v0.1/tests/lib_test.rego +89 -0
- agentspm-0.1.1/policies/v0.1/tests/sc_001_test.rego +47 -0
- agentspm-0.1.1/pyproject.toml +92 -0
- agentspm-0.1.1/schema/examples/agent-ai-act.yaml +40 -0
- agentspm-0.1.1/schema/examples/agent.yaml +45 -0
- agentspm-0.1.1/schema/examples/identity.yaml +19 -0
- agentspm-0.1.1/schema/examples/model.yaml +16 -0
- agentspm-0.1.1/schema/examples/tool-mcp.yaml +28 -0
- agentspm-0.1.1/schema/v0.1/agentspm.schema.json +765 -0
- agentspm-0.1.1/schema/v0.1/findings.schema.json +57 -0
- agentspm-0.1.1/scripts/anchor_lint.py +178 -0
- agentspm-0.1.1/scripts/build_release.sh +41 -0
- agentspm-0.1.1/scripts/default_pass_lint.py +41 -0
- agentspm-0.1.1/scripts/generate_controls_md.py +164 -0
- agentspm-0.1.1/scripts/identifier_scan.py +267 -0
- agentspm-0.1.1/scripts/language_lint.py +154 -0
- agentspm-0.1.1/scripts/probe_live_gateway.py +132 -0
- agentspm-0.1.1/scripts/setup-hooks.sh +9 -0
- agentspm-0.1.1/scripts/unknown_if_lint.py +60 -0
- agentspm-0.1.1/scripts/validate_examples.py +46 -0
- agentspm-0.1.1/src/agentspm/__init__.py +6 -0
- agentspm-0.1.1/src/agentspm/canonical.py +63 -0
- agentspm-0.1.1/src/agentspm/cli.py +624 -0
- agentspm-0.1.1/src/agentspm/collectors/__init__.py +173 -0
- agentspm-0.1.1/src/agentspm/collectors/base.py +402 -0
- agentspm-0.1.1/src/agentspm/collectors/entra.py +603 -0
- agentspm-0.1.1/src/agentspm/collectors/gateway.py +357 -0
- agentspm-0.1.1/src/agentspm/collectors/gitrepo.py +465 -0
- agentspm-0.1.1/src/agentspm/collectors/mock_gateway.py +91 -0
- agentspm-0.1.1/src/agentspm/doctor.py +390 -0
- agentspm-0.1.1/src/agentspm/evaluate.py +288 -0
- agentspm-0.1.1/src/agentspm/graph/__init__.py +31 -0
- agentspm-0.1.1/src/agentspm/graph/derive.py +259 -0
- agentspm-0.1.1/src/agentspm/graph/merge.py +495 -0
- agentspm-0.1.1/src/agentspm/graph/store.py +353 -0
- agentspm-0.1.1/src/agentspm/gwverify.py +384 -0
- agentspm-0.1.1/src/agentspm/mockgw/__init__.py +28 -0
- agentspm-0.1.1/src/agentspm/mockgw/fixtures.py +229 -0
- agentspm-0.1.1/src/agentspm/mockgw/keys.py +101 -0
- agentspm-0.1.1/src/agentspm/mockgw/server.py +188 -0
- agentspm-0.1.1/src/agentspm/models.py +274 -0
- agentspm-0.1.1/src/agentspm/purge.py +196 -0
- agentspm-0.1.1/src/agentspm/py.typed +0 -0
- agentspm-0.1.1/src/agentspm/report/__init__.py +224 -0
- agentspm-0.1.1/src/agentspm/report/json.py +21 -0
- agentspm-0.1.1/src/agentspm/report/md.py +199 -0
- agentspm-0.1.1/src/agentspm/report/redact.py +278 -0
- agentspm-0.1.1/src/agentspm/report/sarif.py +151 -0
- agentspm-0.1.1/src/agentspm/validate.py +184 -0
- agentspm-0.1.1/tests/__init__.py +0 -0
- agentspm-0.1.1/tests/cli/__init__.py +0 -0
- agentspm-0.1.1/tests/cli/test_cli.py +511 -0
- agentspm-0.1.1/tests/collectors/__init__.py +0 -0
- agentspm-0.1.1/tests/collectors/conftest.py +91 -0
- agentspm-0.1.1/tests/collectors/test_base.py +153 -0
- agentspm-0.1.1/tests/collectors/test_entra.py +423 -0
- agentspm-0.1.1/tests/collectors/test_gateway_collector.py +302 -0
- agentspm-0.1.1/tests/collectors/test_gitrepo.py +276 -0
- agentspm-0.1.1/tests/collectors/test_runner.py +171 -0
- agentspm-0.1.1/tests/conftest.py +45 -0
- agentspm-0.1.1/tests/contract/__init__.py +1 -0
- agentspm-0.1.1/tests/contract/conftest.py +87 -0
- agentspm-0.1.1/tests/contract/openapi_adapter.py +81 -0
- agentspm-0.1.1/tests/contract/test_agents_contract.py +234 -0
- agentspm-0.1.1/tests/contract/test_auth_branches.py +73 -0
- agentspm-0.1.1/tests/contract/test_events_contract.py +90 -0
- agentspm-0.1.1/tests/contract/test_inventory_contract.py +97 -0
- agentspm-0.1.1/tests/e2e/__init__.py +0 -0
- agentspm-0.1.1/tests/e2e/test_quickstart.py +148 -0
- agentspm-0.1.1/tests/graph/__init__.py +1 -0
- agentspm-0.1.1/tests/graph/conftest.py +92 -0
- agentspm-0.1.1/tests/graph/test_derive.py +186 -0
- agentspm-0.1.1/tests/graph/test_graph_hash.py +80 -0
- agentspm-0.1.1/tests/graph/test_merge.py +250 -0
- agentspm-0.1.1/tests/graph/test_store.py +84 -0
- agentspm-0.1.1/tests/report/__init__.py +0 -0
- agentspm-0.1.1/tests/report/test_redact.py +159 -0
- agentspm-0.1.1/tests/report/test_render.py +222 -0
- agentspm-0.1.1/tests/test_anchor_lint.py +104 -0
- agentspm-0.1.1/tests/test_external_ref.py +66 -0
- agentspm-0.1.1/tests/test_gwverify.py +88 -0
- agentspm-0.1.1/tests/test_identifier_scan.py +82 -0
- agentspm-0.1.1/tests/test_language_lint.py +74 -0
- agentspm-0.1.1/tests/test_models.py +105 -0
- agentspm-0.1.1/tests/test_opa_pin_consistency.py +56 -0
- agentspm-0.1.1/tests/test_policy_findings.py +160 -0
- agentspm-0.1.1/tests/test_policy_pack.py +178 -0
- agentspm-0.1.1/tests/test_purge.py +188 -0
- agentspm-0.1.1/tests/test_schema.py +133 -0
- 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: []
|