certone-sdk 0.2.0__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.
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ *.pyc
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+ .venv/
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.5
2
+ Name: certone-sdk
3
+ Version: 0.2.0
4
+ Summary: CertOne SDK — typed API client, certone-submit (confidential scan + verdict gate) and the run-at-source agent.
5
+ Project-URL: Homepage, https://certkor.ai
6
+ Author: Certkor AI
7
+ License: Proprietary
8
+ Keywords: ai-compliance,certification,ci,confidential-computing,eu-ai-act
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: Other/Proprietary License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Quality Assurance
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: requests>=2.31
21
+ Provides-Extra: agent
22
+ Requires-Dist: certone-engine>=0.1.0; extra == 'agent'
23
+ Provides-Extra: azure
24
+ Requires-Dist: azure-identity>=1.15; extra == 'azure'
25
+ Requires-Dist: azure-keyvault-secrets>=4.7; extra == 'azure'
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy>=1.11; extra == 'dev'
28
+ Requires-Dist: pytest>=8.0; extra == 'dev'
29
+ Requires-Dist: ruff>=0.6; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # certone-sdk
33
+
34
+ The Python SDK and command-line tools for [CertOne](https://certkor.ai), Certkor's AI compliance
35
+ certification platform. Send an AI project for a confidential scan from any pipeline, notebook,
36
+ editor or device, gate on the verdict, and fetch the evidence and the attestation.
37
+
38
+ CertOne evaluates the project inside a hardware-attested trusted execution environment (AMD SEV-SNP
39
+ confidential VMs, or Kata confidential containers): it builds the AI bill of materials, checks it
40
+ against the policy packs for the regions where the system runs (EU AI Act, NIST AI RMF, ISO/IEC
41
+ 42001, sector rules...), runs dynamic tests and computes the evaluation metrics.
42
+
43
+ You need a CertOne tenant and an API key (Developer Hub -> API keys, `member` role).
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install certone-sdk
49
+ ```
50
+
51
+ Python 3.10+. Extras: `certone-sdk[azure]` reads the API key from Azure Key Vault with the compute's
52
+ managed identity.
53
+
54
+ ## Command line: `certone-submit`
55
+
56
+ One command for any CI/CD system. It packs the project, submits it for a confidential scan, shows
57
+ progress until the verdict, and exits `0` on pass, `1` on fail, `2` on error -- so the job stops on a
58
+ failing certification.
59
+
60
+ ```bash
61
+ export CERTONE_API_KEY=ck_... # a tenant member key
62
+ certone-submit --project . --asset "Fraud Scorer" --country-code EU --environment ci
63
+ ```
64
+
65
+ | option | |
66
+ |---|---|
67
+ | `--asset` | AI system name in CertOne (required) |
68
+ | `--country-code` | region the system operates in, e.g. `EU`, `US`, `CA` |
69
+ | `--environment` | `ci`, `jupyter`, `vscode`, `edge`, `api`, `cloud`, `cli` |
70
+ | `--tee` | `amd_sev_snp` (default) or `kata_cc` |
71
+ | `--fail-on` | lowest severity that fails the gate: `low`, `medium`, `high` (default), `none` |
72
+ | `--api` | platform URL (default `$CERTONE_API_URL`, else `https://platform.certkor.ai`) |
73
+ | `--api-key-vault` | read the key from `https://<vault>.vault.azure.net/secrets/<name>` |
74
+ | `--report-out`, `--attestation-out`, `--markdown-out` | write the report, the attestation (passing verdicts) or a markdown summary (e.g. `$GITHUB_STEP_SUMMARY`) |
75
+
76
+ ## Python
77
+
78
+ ```python
79
+ import os
80
+ from certone_sdk import CertOneClient, ProgressPrinter, submit_and_wait
81
+
82
+ client = CertOneClient("https://platform.certkor.ai", os.environ["CERTONE_API_KEY"])
83
+ sub = submit_and_wait(client, "rag-service", asset="RAG Answering Service", tee="kata_cc",
84
+ country_code="EU", on_progress=ProgressPrinter())
85
+ print(sub.verdict, sub.report["by_severity"])
86
+ for issue in sub.document["issues"]:
87
+ print(issue["severity"], issue["description"])
88
+ if sub.passed:
89
+ print(sub.attestation["attestation_id"])
90
+ ```
91
+
92
+ The client also covers the rest of the API: `list_systems()`, `observability_summary()`,
93
+ `get_run()` (where the scan executed and the TEE's hardware attestation), `get_report_document()`,
94
+ `get_attestation()`.
95
+
96
+ ## Agent: `certone-agent`
97
+
98
+ For devices and long-running services: keep certifying a project as it changes.
99
+
100
+ ```bash
101
+ # re-scan confidentially whenever the project's content changes
102
+ certone-agent watch --mode confidential --project /opt/model --asset "Line Robot Picker" \
103
+ --api https://platform.certkor.ai --environment edge --country-code EU --interval 600
104
+ ```
105
+
106
+ `--mode local` runs the evaluation where the code lives and submits only the results (needs the
107
+ CertOne engine, `certone-sdk[agent]`, available to CertOne customers). `certone-agent deliver`
108
+ uploads a result signed inside an air-gapped network (`certone result sign`); the platform verifies
109
+ it against the source keys registered for the tenant.
110
+
111
+ ## Licence
112
+
113
+ Proprietary. Use requires a CertOne subscription; see https://certkor.ai.
@@ -0,0 +1,82 @@
1
+ # certone-sdk
2
+
3
+ The Python SDK and command-line tools for [CertOne](https://certkor.ai), Certkor's AI compliance
4
+ certification platform. Send an AI project for a confidential scan from any pipeline, notebook,
5
+ editor or device, gate on the verdict, and fetch the evidence and the attestation.
6
+
7
+ CertOne evaluates the project inside a hardware-attested trusted execution environment (AMD SEV-SNP
8
+ confidential VMs, or Kata confidential containers): it builds the AI bill of materials, checks it
9
+ against the policy packs for the regions where the system runs (EU AI Act, NIST AI RMF, ISO/IEC
10
+ 42001, sector rules...), runs dynamic tests and computes the evaluation metrics.
11
+
12
+ You need a CertOne tenant and an API key (Developer Hub -> API keys, `member` role).
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install certone-sdk
18
+ ```
19
+
20
+ Python 3.10+. Extras: `certone-sdk[azure]` reads the API key from Azure Key Vault with the compute's
21
+ managed identity.
22
+
23
+ ## Command line: `certone-submit`
24
+
25
+ One command for any CI/CD system. It packs the project, submits it for a confidential scan, shows
26
+ progress until the verdict, and exits `0` on pass, `1` on fail, `2` on error -- so the job stops on a
27
+ failing certification.
28
+
29
+ ```bash
30
+ export CERTONE_API_KEY=ck_... # a tenant member key
31
+ certone-submit --project . --asset "Fraud Scorer" --country-code EU --environment ci
32
+ ```
33
+
34
+ | option | |
35
+ |---|---|
36
+ | `--asset` | AI system name in CertOne (required) |
37
+ | `--country-code` | region the system operates in, e.g. `EU`, `US`, `CA` |
38
+ | `--environment` | `ci`, `jupyter`, `vscode`, `edge`, `api`, `cloud`, `cli` |
39
+ | `--tee` | `amd_sev_snp` (default) or `kata_cc` |
40
+ | `--fail-on` | lowest severity that fails the gate: `low`, `medium`, `high` (default), `none` |
41
+ | `--api` | platform URL (default `$CERTONE_API_URL`, else `https://platform.certkor.ai`) |
42
+ | `--api-key-vault` | read the key from `https://<vault>.vault.azure.net/secrets/<name>` |
43
+ | `--report-out`, `--attestation-out`, `--markdown-out` | write the report, the attestation (passing verdicts) or a markdown summary (e.g. `$GITHUB_STEP_SUMMARY`) |
44
+
45
+ ## Python
46
+
47
+ ```python
48
+ import os
49
+ from certone_sdk import CertOneClient, ProgressPrinter, submit_and_wait
50
+
51
+ client = CertOneClient("https://platform.certkor.ai", os.environ["CERTONE_API_KEY"])
52
+ sub = submit_and_wait(client, "rag-service", asset="RAG Answering Service", tee="kata_cc",
53
+ country_code="EU", on_progress=ProgressPrinter())
54
+ print(sub.verdict, sub.report["by_severity"])
55
+ for issue in sub.document["issues"]:
56
+ print(issue["severity"], issue["description"])
57
+ if sub.passed:
58
+ print(sub.attestation["attestation_id"])
59
+ ```
60
+
61
+ The client also covers the rest of the API: `list_systems()`, `observability_summary()`,
62
+ `get_run()` (where the scan executed and the TEE's hardware attestation), `get_report_document()`,
63
+ `get_attestation()`.
64
+
65
+ ## Agent: `certone-agent`
66
+
67
+ For devices and long-running services: keep certifying a project as it changes.
68
+
69
+ ```bash
70
+ # re-scan confidentially whenever the project's content changes
71
+ certone-agent watch --mode confidential --project /opt/model --asset "Line Robot Picker" \
72
+ --api https://platform.certkor.ai --environment edge --country-code EU --interval 600
73
+ ```
74
+
75
+ `--mode local` runs the evaluation where the code lives and submits only the results (needs the
76
+ CertOne engine, `certone-sdk[agent]`, available to CertOne customers). `certone-agent deliver`
77
+ uploads a result signed inside an air-gapped network (`certone result sign`); the platform verifies
78
+ it against the source keys registered for the tenant.
79
+
80
+ ## Licence
81
+
82
+ Proprietary. Use requires a CertOne subscription; see https://certkor.ai.
@@ -0,0 +1,27 @@
1
+ """CertOne SDK — a typed API client + the run-at-source agent.
2
+
3
+ from certone_sdk import CertOneClient
4
+ c = CertOneClient("https://api.yourco.com", token)
5
+ c.ingest_report(report, asset="Fraud Scorer", environment="ci", country_code="EU")
6
+
7
+ ``certone-submit`` sends a project for a confidential scan and gates on the verdict (the path every
8
+ CI/MLOps/editor integration uses). The ``certone-agent`` CLI runs the engine where your code lives
9
+ and submits only the results, for systems that may not leave your environment.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from certone_sdk.client import CertOneClient, CertOneError, sha256_digest
15
+ from certone_sdk.submit import ProgressPrinter, Submission, pack_project, submit_and_wait
16
+
17
+ __all__ = [
18
+ "CertOneClient",
19
+ "CertOneError",
20
+ "ProgressPrinter",
21
+ "Submission",
22
+ "__version__",
23
+ "pack_project",
24
+ "sha256_digest",
25
+ "submit_and_wait",
26
+ ]
27
+ __version__ = "0.2.0"
@@ -0,0 +1,274 @@
1
+ """certone-agent — certify an AI project from where it lives (a gateway, a build box, a device).
2
+
3
+ Two modes, for CI/cron (`run`, one-shot) or a long-running watcher (`watch`):
4
+
5
+ * ``--mode local`` (default, run-at-source): the model/code never leaves your environment. The
6
+ agent builds the AI BOM + runs the verdict loop locally, then POSTs the **report** (plus a content
7
+ digest that binds the ⑥ attestation) to the control plane via the SDK.
8
+ * ``--mode confidential``: the project is sent to the platform and processed inside an attested
9
+ confidential worker (static + dynamic tests on the target + metrics + the in-TEE correction),
10
+ exactly as ``certone-submit`` does. ``watch`` re-submits only when the project's content changes,
11
+ so a gateway re-certifies each new model version as it lands.
12
+
13
+ The API key comes from ``--token`` or ``$CERTONE_API_KEY`` (preferred: it then never appears in the
14
+ process list).
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import hashlib
21
+ import json
22
+ import os
23
+ import sys
24
+ import time
25
+ from pathlib import Path
26
+ from typing import Any
27
+
28
+ from certone_sdk.client import CertOneClient, CertOneError
29
+ from certone_sdk.submit import SKIP_DIRS, pack_project, submit_and_wait
30
+
31
+
32
+ def run_once(
33
+ project: str,
34
+ pack_paths: list[str],
35
+ *,
36
+ client: CertOneClient,
37
+ asset: str | None = None,
38
+ environment: str = "ci",
39
+ country_code: str = "ZZ",
40
+ fail_on: str = "high",
41
+ snapshot_ref: str | None = None,
42
+ repo: str | None = None,
43
+ commit_hash: str | None = None,
44
+ ) -> dict[str, Any]:
45
+ """Run the engine over ``project`` against ``pack_paths``; submit the report (run-at-source).
46
+
47
+ Pass ``repo`` + ``commit_hash`` (or run inside GitHub Actions, where they're auto-detected) to
48
+ close the two-way loop: the verdict is posted back as a native Check Run on that commit.
49
+ """
50
+ from certone_engine.archive import make_targz
51
+ from certone_engine.evaluation.runner import run_check
52
+
53
+ result = run_check(project, list(pack_paths), fail_on=fail_on, snapshot_ref=snapshot_ref)
54
+ report = result.report.to_dict()
55
+ # Bind the attestation to the snapshot content without uploading it.
56
+ digest = "sha256:" + hashlib.sha256(make_targz(Path(project))).hexdigest()
57
+ return client.ingest_report(
58
+ report,
59
+ asset=asset,
60
+ environment=environment,
61
+ country_code=country_code,
62
+ snapshot_ref=snapshot_ref,
63
+ input_digest=digest,
64
+ repo=repo,
65
+ commit_hash=commit_hash,
66
+ )
67
+
68
+
69
+ def content_digest(project: str | Path) -> str:
70
+ """``sha256:`` over the project's files (paths + bytes), skipping VCS metadata and caches --
71
+ what ``watch`` compares to decide whether a new version landed."""
72
+ root = Path(project).resolve()
73
+ h = hashlib.sha256()
74
+ for path in sorted(root.rglob("*")):
75
+ rel = path.relative_to(root)
76
+ if path.is_file() and not path.is_symlink() and not SKIP_DIRS.intersection(rel.parts):
77
+ h.update(rel.as_posix().encode() + b"\0")
78
+ with path.open("rb") as fh:
79
+ for chunk in iter(lambda: fh.read(1 << 20), b""):
80
+ h.update(chunk)
81
+ return f"sha256:{h.hexdigest()}"
82
+
83
+
84
+ def run_confidential(
85
+ project: str | Path,
86
+ *,
87
+ client: CertOneClient,
88
+ asset: str | None = None,
89
+ environment: str = "edge",
90
+ country_code: str = "ZZ",
91
+ fail_on: str = "high",
92
+ tee: str = "amd_sev_snp",
93
+ snapshot_ref: str | None = None,
94
+ commit_hash: str | None = None,
95
+ ) -> str:
96
+ """Send ``project`` for a confidential scan and print its verdict; returns the verdict."""
97
+ root = Path(project).resolve()
98
+ name = asset or root.name
99
+ sub = submit_and_wait(
100
+ client,
101
+ pack_project(root),
102
+ asset=name,
103
+ environment=environment,
104
+ country_code=country_code,
105
+ fail_on=fail_on,
106
+ tee=tee,
107
+ snapshot_ref=snapshot_ref,
108
+ commit_hash=commit_hash,
109
+ on_submitted=lambda scan_id, digest: print(
110
+ f"certone-agent: {name} -> confidential scan {scan_id} ({digest})", flush=True
111
+ ),
112
+ )
113
+ by = sub.report.get("by_severity") or {}
114
+ runtime = (sub.run.get("execution") or {}).get("runtime") or "the confidential worker"
115
+ att = sub.attestation.get("attestation_id") if sub.attestation else None
116
+ attestation = f"; attestation {att}" if att else ""
117
+ print(
118
+ f"certone-agent: verdict {sub.verdict.upper()} ({by.get('high', 0)} high, "
119
+ f"{by.get('medium', 0)} medium, {by.get('low', 0)} low) in {runtime}{attestation}",
120
+ flush=True,
121
+ )
122
+ return sub.verdict
123
+
124
+
125
+ def watch_confidential(scan: Any, project: str | Path, interval: int, *, polls: int = 0) -> None:
126
+ """Call ``scan()`` for the first version of ``project`` and again whenever its content changes.
127
+
128
+ A failed scan is retried on the next poll. ``polls`` > 0 bounds the loop (tests); 0 = forever.
129
+ """
130
+ seen: str | None = None
131
+ done = 0
132
+ while polls <= 0 or done < polls:
133
+ digest = content_digest(project)
134
+ if digest != seen:
135
+ if seen is not None:
136
+ print(f"certone-agent: new version detected ({digest})", flush=True)
137
+ try:
138
+ scan()
139
+ seen = digest
140
+ except CertOneError as exc:
141
+ print(f"certone-agent: {exc}", file=sys.stderr, flush=True)
142
+ done += 1
143
+ if polls <= 0 or done < polls:
144
+ time.sleep(max(1, interval))
145
+
146
+
147
+ def _ci_source(repo: str | None, commit_hash: str | None) -> tuple[str | None, str | None]:
148
+ """Fill repo/commit from the GitHub Actions environment when not given explicitly."""
149
+ return (
150
+ repo or os.environ.get("GITHUB_REPOSITORY") or None,
151
+ commit_hash or os.environ.get("GITHUB_SHA") or None,
152
+ )
153
+
154
+
155
+ def _add_common(p: argparse.ArgumentParser) -> None:
156
+ p.add_argument("--project", default=".", help="path to the AI project (default: .)")
157
+ p.add_argument(
158
+ "--mode",
159
+ choices=("local", "confidential"),
160
+ default="local",
161
+ help="local: run at source and submit the report; confidential: scan in the platform TEE",
162
+ )
163
+ p.add_argument(
164
+ "--pack", action="append", default=[], dest="packs", help="PaC pack dir (repeatable)"
165
+ )
166
+ p.add_argument(
167
+ "--api", required=True, help="control-plane base URL, e.g. https://api.yourco.com"
168
+ )
169
+ p.add_argument("--token", default=None, help="API token, member role ($CERTONE_API_KEY)")
170
+ p.add_argument("--asset", help="AI system name (auto from snapshot-ref if omitted)")
171
+ p.add_argument("--environment", default=None, help="ci|jupyter|vscode|edge|api|cloud|cli")
172
+ p.add_argument("--country-code", default="ZZ", help="origin region, e.g. EU")
173
+ p.add_argument("--fail-on", default="high", help="low|medium|high|none")
174
+ p.add_argument("--tee", default="amd_sev_snp", help="amd_sev_snp | kata_cc (confidential)")
175
+ p.add_argument("--snapshot-ref", help="stable source ref, e.g. git:repo@sha")
176
+ p.add_argument("--repo", help="GitHub repo 'owner/name' for native PR checks (auto in Actions)")
177
+ p.add_argument("--commit", help="head SHA to post the Check Run on (auto in Actions)")
178
+
179
+
180
+ def deliver(client: CertOneClient, envelope_path: str | Path) -> dict[str, Any]:
181
+ """Deliver a result signed inside an air-gapped network (``certone result sign``)."""
182
+ envelope = json.loads(Path(envelope_path).read_text(encoding="utf-8"))
183
+ return client.ingest_signed_result(envelope)
184
+
185
+
186
+ def main(argv: list[str] | None = None) -> int:
187
+ parser = argparse.ArgumentParser(prog="certone-agent", description=__doc__)
188
+ sub = parser.add_subparsers(dest="cmd", required=True)
189
+ run_p = sub.add_parser("run", help="run once and submit (CI/cron)")
190
+ _add_common(run_p)
191
+ watch_p = sub.add_parser("watch", help="keep certifying (long-running)")
192
+ _add_common(watch_p)
193
+ watch_p.add_argument("--interval", type=int, default=300, help="seconds between checks")
194
+ deliver_p = sub.add_parser(
195
+ "deliver", help="deliver a signed result carried out of an air-gapped network"
196
+ )
197
+ deliver_p.add_argument(
198
+ "envelope", help="the result.signed.json written by `certone result sign`"
199
+ )
200
+ deliver_p.add_argument("--api", required=True, help="control-plane base URL")
201
+ deliver_p.add_argument("--token", default=None, help="API token ($CERTONE_API_KEY)")
202
+ args = parser.parse_args(argv)
203
+
204
+ token = args.token or os.environ.get("CERTONE_API_KEY")
205
+ if not token:
206
+ parser.error("no API key: pass --token or set CERTONE_API_KEY")
207
+ client = CertOneClient(args.api, token)
208
+
209
+ if args.cmd == "deliver":
210
+ try:
211
+ out = deliver(client, args.envelope)
212
+ except CertOneError as exc:
213
+ print(f"certone-agent: delivery refused: {exc}", file=sys.stderr)
214
+ return 2
215
+ report = out.get("report") or {}
216
+ print(
217
+ f"certone-agent: signed result accepted (signature verified): scan "
218
+ f"{out['scan_run']['id']}, verdict {str(report.get('verdict_status', '?')).upper()}"
219
+ )
220
+ return 0
221
+ repo, commit_hash = _ci_source(args.repo, args.commit)
222
+
223
+ if args.mode == "confidential":
224
+
225
+ def scan() -> str:
226
+ return run_confidential(
227
+ args.project,
228
+ client=client,
229
+ asset=args.asset,
230
+ environment=args.environment or "edge",
231
+ country_code=args.country_code,
232
+ fail_on=args.fail_on,
233
+ tee=args.tee,
234
+ snapshot_ref=args.snapshot_ref,
235
+ commit_hash=commit_hash,
236
+ )
237
+
238
+ if args.cmd == "watch":
239
+ print(f"certone-agent: watching {Path(args.project).resolve()}", flush=True)
240
+ watch_confidential(scan, args.project, args.interval)
241
+ return 0
242
+ try:
243
+ return 0 if scan() == "pass" else 1
244
+ except CertOneError as exc:
245
+ print(f"certone-agent: {exc}", file=sys.stderr)
246
+ return 2
247
+
248
+ def _go() -> str:
249
+ out = run_once(
250
+ args.project,
251
+ args.packs,
252
+ client=client,
253
+ asset=args.asset,
254
+ environment=args.environment or "ci",
255
+ country_code=args.country_code,
256
+ fail_on=args.fail_on,
257
+ snapshot_ref=args.snapshot_ref,
258
+ repo=repo,
259
+ commit_hash=commit_hash,
260
+ )
261
+ return str(out.get("report", {}).get("verdict_status", "?"))
262
+
263
+ if args.cmd == "watch":
264
+ while True:
265
+ verdict = _go()
266
+ print(f"certone-agent: submitted (verdict={verdict})")
267
+ time.sleep(max(5, args.interval))
268
+ verdict = _go()
269
+ print(f"certone-agent: submitted (verdict={verdict})")
270
+ return 0
271
+
272
+
273
+ if __name__ == "__main__": # pragma: no cover
274
+ raise SystemExit(main())