Skip to main content

veltro-ai

Apache-2.0 shared Python client for the Veltro AI provider contract (veltro-ai-provider/v1).

Mirrors @veltrosecurity/ai: config validation, secret redaction, Draft 2020-12 structured-output validation, invocation audit digests, and provider adapters whose injectable transport may return a response directly or as an awaitable.

verdict = parse_structured_output(
    model_text,
    schema={
        "type": "object",
        "properties": {
            "verdict": {
                "type": "string",
                "enum": ["true_positive", "false_positive"],
            },
            "confidence": {"type": "number", "minimum": 0, "maximum": 1},
        },
        "required": ["verdict", "confidence"],
        "additionalProperties": False,
    },
)

Malformed schemas raise ai_invalid_config. Invalid model payloads raise ai_structured_output_invalid without including model values in the error.

Cross-language guarantee. For a provider config, a base_url, a text to redact, or a structured-output schema and payload, this package and @veltrosecurity/ai return the same value or the same refusal code — never opposite answers. Where the two runtimes cannot be made to agree on a construct, both refuse it rather than answering differently, and every such construct is named below. One difference remains, and it is not in the answer: a payload or schema nested deeper than the runtime's own stack is refused (ai_structured_output_invalid for a payload, ai_invalid_config for a schema), and the depth at which each runtime gives up is a property of the runtime, not of this contract.

A schema is always enforced as Draft 2020-12 whatever dialect its $schema announces, and format is an annotation only. Draft 2020-12 is enforced exactly: draft-07 dependencies is an inert unknown keyword (use dependentRequired/dependentSchemas), a draft-04 id is refused as ai_invalid_config, and an if with no then/else asserts nothing. Every $ref a validator could reach is resolved before the first payload is seen — jsonschema resolves lazily, so a reference on a branch the payload happens not to exercise would otherwise never be checked — which means a reference that does not resolve inside the schema, remote or dangling into $defs, is refused as ai_invalid_config whatever the payload looks like, and is never retrieved over the network. Nothing retrieves one in either client: Draft202012Validator's default registry is empty and refuses offline, and the bundled specification registry this client passes instead is what makes the dialect metaschemas resolvable — HTTP retrieval belonged to the deprecated RefResolver, which this client does not use. A $ref chain that returns to itself through allOf/anyOf/oneOf/not alone can never validate anything and is refused; a genuinely recursive schema (through properties, items, …) is accepted. A broken reference in an unreferenced $defs entry, or a $ref-shaped const value, is data and stays accepted. There is exactly one position none of that reaches: a subschema under an if with no then/else. That keyword asserts nothing, and both clients drop it before validating — this client deletes the key and Ajv never compiles it — so a remote $ref, a $ref cycle, a $dynamicRef or a pattern outside the accepted subset is accepted and inert there in both clients, and nothing under it is ever resolved or compiled. Two vectors pin that, and every refusal below is about the schema both clients validate, which never carries a lone if.

$dynamicRef, $dynamicAnchor, $recursiveRef and $recursiveAnchor are refused as ai_invalid_config — everywhere except under the lone if above. Their target depends on the dynamic scope of the instance, and jsonschema and Ajv disagree about a dangling one, so neither client accepts either spelling: use $ref into $defs.

pattern, patternProperties keys and propertyNames.pattern are ECMA-262 expressions, not Python re expressions, and only the subset both engines read identically is accepted — anything else is ai_invalid_config, in both clients, wherever it sits in the document (including an unreferenced $defs), the lone if above excepted. Accepted: literals, ., ^, $, |, groups (…)/(?:…), lookahead (?=…)/(?!…), character classes with ranges and negation, \d/\D/\w/ \W (\D/\W outside a class), \b/\B, \n/\r/\t/\f/\v, \xHH, \uHHHH, escaped metacharacters, and quantifiers */+/?/{m}/{m,}/ {m,n} with an optional lazy ? and bounds ≤65535. Refused: \s/\S (the two whitespace sets differ), \p{…}, \A/\Z, named groups in either spelling, lookbehind, inline flags, (?#…), backreferences, possessive quantifiers, a{,n}, [], [^], an unescaped [/]/{/}, a lone surrogate escape, and a range whose ends are not single characters. The accepted subset is translated into the Python spelling of the same ECMA-262 expression, so the two clients agree on the answer, not only on acceptance: $ is end of input (so ^ok$ refuses "ok\n", which Python's $ would have accepted), . excludes every line terminator, and \d, \w and \b are ASCII.

base_url is http/https with an ASCII host (a hostname, a dotted IPv4, or a bracketed plain-hex IPv6), an optional port in 1..65535, and an optional path; userinfo, a query, a fragment, whitespace, a control character, a percent-escape in the host and a non-ASCII host are refused as ai_invalid_config. That one shape — not urlparse, and not JavaScript's new URL() — is what both clients apply, so a stored value is accepted or refused identically, and is_ai_configured/is_ai_enabled/build_ai_status answer False for a bad one instead of raising a bare ValueError.

conformance/veltro-ai-provider-v1/vectors.json pins every decision above, and both suites execute all of it.

redact_secrets masks provider and cloud credential shapes as issued today — sk-/sk-proj-/sk-svcacct-/sk-ant-api03-, Google AIza, GitHub ghp_ and github_pat_, Slack xox*-, AWS AKIA/ASIA key ids, Bearer values, base64-shaped Basic credentials, the value of an Authorization/Proxy-Authorization header, an AWS4-HMAC-SHA256 credential string wherever it sits, a signature parameter of sixteen characters or more, JWTs, and …secret…/…token…/…key… assignments — and runs both on every provider response body before it can reach an ai_provider_error message and on every message and system prompt before the transport sees it. Ordinary prose, digests and commit SHAs pass through unchanged.

What "the value" means. The name is one that ends in Authorization or Proxy-Authorization after a character that is not a letter, digit or _, so a dashed vendor prefix comes along (X-Authorization:, x-amz-authorization:) and a name that merely contains the word does not (deauthorization:, my_authorization:). It is read as name: value, name=value, headers["name"] = "value", or the JSON, escaped-JSON and curl -H quoting of any of those, with the value on the name's own line. The mask covers every ,/;-delimited parameter of the value — so an RFC 7616 Digest response=, a SigV4 Signature= and a Hawk mac= go with the parameter that precedes them instead of surviving in the outbound prompt — and a space extends it only into another credential-shaped run, so Authorization: NTLM TlRMTVNTUAABAAAA rejected by upstream keeps its last three words.

What has to look like a credential. Because redact_messages runs on every message, exactly one thing is shape-tested: the value itself, or the token after a recognised scheme. The recognised set is exported as AI_AUTH_SCHEMES — AI_REGISTERED_AUTH_SCHEMES and AI_VENDOR_AUTH_SCHEMES are its two halves — so a consumer can check its own header instead of guessing. It is all fourteen names in the IANA HTTP Authentication Scheme registry (Basic, Bearer, Concealed, DPoP, Digest, GNAP, HOBA, Mutual, Negotiate, OAuth, PrivateToken, SCRAM-SHA-1, SCRAM-SHA-256, vapid) plus twenty-one schemes real services issue that no registry governs, each taken from its vendor's own documentation: Okta's SSWS, Discord's Bot, Splunk, Zoho's Zoho-oauthtoken and Zoho-authtoken, Opsgenie's GenieKey, acquia-http-hmac, the OAuth MAC draft, Klaviyo-API-Key, GitHub's Token, Elasticsearch's ApiKey, Azure's SharedKey, SharedKeyLite and SharedAccessSignature, AWS4-HMAC-SHA256 and its predecessor AWS, Kerberos, NTLM, Hawk, WSSE and Signature. Credential shape is eight characters or more, or a key=value parameter, and not ordinary text. Ordinary text is one word — ALL-CAPS, Capitalised or lower-case, at most fifteen characters a part, up to five parts joined by -, _ or an apostrophe, an optional bracketed word the way Optional[str] spells one, and optional closing sentence punctuation — or one key=value whose key is such a word and whose value is such a word or a number of up to four digits, with no further parameter after it. Case and length are ASCII notions, so a value carrying any character outside ASCII is ordinary text whatever its case: approuvé, одобрено, 承認済み and อนุมัติ all survive, and none of them survives on a character count.

So authorization: approved tenant=acme, authorization: denied reason=policy_violation, authorization: approved 2026-09-06T19:00:00Z, Authorization: Token expired, authorization: role_admin, authorization: scope=read, Authorization: n/a, authorization: 3 of 5 approvers signed and {"authorization": "approved"} survive byte for byte — in an outbound prompt and in the ai_provider_error built from a failing response — while Authorization: NTLM TlRMTVNTUAABAAAA, a reordered authorization: qop=auth, response="…" and a schemeless authorization: 4a5b6c7d8e9f0a1b2c3d4e5f60718293 do not.

One accepted over-masking edge is contractual: when an otherwise ordinary value starts with Bearer, PrivateToken, GenieKey, SharedKey, SharedKeyLite, SharedAccessSignature, SCRAM-SHA-1, SCRAM-SHA-256 or AWS4-HMAC-SHA256, that recognised leading scheme name is itself masked (for example, authorization: PrivateToken expired becomes authorization: [REDACTED_AUTH] expired). The shared vectors pin this outcome; it is not a promise that every recognised name survives when followed by prose.

Where it stops. This is defence in depth behind a product's own secret handling, not a sanitiser, and it does not promise to find a credential that is not spelled like one. It does not read a name that merely starts with the word (Authorization-Header:), a name held away from its : or = by anything but spaces and tabs, or a value that begins on the next line. It does not read the WWW-Authenticate: and Proxy-Authenticate: challenges, which carry a server's realm rather than a client's proof — though an AWS4-HMAC-SHA256 string inside one is still masked. Its largest gap is deliberate: a scheme that is not in AI_AUTH_SCHEMES and whose name is itself ordinary text leaves its credential alone (Authorization: Vendor abc.def-ghi, or whatever the next vendor invents — Authorization: Acme 4a5b6c7d8e9f0a1b), while one that is not ordinary text is masked whole (Authorization: X-Vendor2 abc.def-ghi). Re-testing the second token whenever the first is merely word-shaped is what closes that gap, and it was measured: it changes 501 of 2,570 realistic prose strings, deleting reason=, actor=, request ids, URLs and timestamps out of log lines. This client is defence in depth behind a product that is not supposed to put credentials in prompts at all, and it reads log lines for a living, so the gap stays and the set grows by name instead — check yours against AI_AUTH_SCHEMES. It also leaves a schemeless value that is ordinary text by the rule above (Authorization: SECRETPASSWORD, Authorization: deadbeefcafe, Authorization: SECRET_PASSWORD_VALUE) — the price of letting a verdict through. conformance/veltro-ai-provider-v1/vectors.json pins all of it in both directions, and redactSecrets in @veltrosecurity/ai applies the same table.

normalize_legacy_ai_settings denies every data class unless the stored blob grants one: a stored allow_data_classes list is authoritative (an empty one is an exact deny-all), otherwise ai_allow_log_samples grants events only when it is exactly True. Product-specific widening belongs in the product adapter, not here.

TeamAiSettings, map_stored_provider_to_kind and normalize_team_ai_settings are the Python counterparts of the TypeScript VectorFlow team adapter. Both adapters map the same stored provider spellings, defaults, key-presence marker and pipeline-data grant, and both execute the shared team vectors.

Metadata

Release files for veltro-ai 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for veltro-ai 0.2.0
File Size Uploaded Provenance Actions
veltro_ai-0.2.0.tar.gz 31.2 kB With provenance Details for veltro_ai-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for veltro-ai 0.2.0
File Interpreter ABI Platform Actions
veltro_ai-0.2.0-py3-none-any.whl Python 3 none any Details for veltro_ai-0.2.0-py3-none-any.whl

Total release size: 62.6 kB

Release files / veltro_ai-0.2.0.tar.gz

Download URL veltro_ai-0.2.0.tar.gz
Size 31.2 kB
Tags Source
SHA-256 checksum
How to use checksums
c3adcfd6bffece12673a42467a6f37fe915945bbcd9345578ab77e5045693035
BLAKE2b-256 checksum
How to use checksums
2b2bb7d43a13f9c94a12af44211219dec9450090f058f907b07aac1f4890af97
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / veltro_ai-0.2.0-py3-none-any.whl

Download URL veltro_ai-0.2.0-py3-none-any.whl
Size 31.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5cc47d182757bef185419d1b0f851ac944b4c71f7f253f8203ed42abff728fa9
BLAKE2b-256 checksum
How to use checksums
ed16aea48716e8588de3bec38e213259b4355a55a9360007db2025f278d8eac4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Security for veltro-ai 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for veltro_ai-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page