Changelog¶
All notable changes to the Silo SDK will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.14.1] - 2026-09-09¶
A dependency and packaging release. No SDK behavior changes: every public method, signature and return shape is identical to 0.14.0. What changed is what a fresh install is allowed to resolve to, and the fact that there is now a second name on PyPI that resolves to the MCP server.
Added¶
- New
silo-sdk-mcpdistribution on PyPI —pip install silo-sdk-mcpnow works. It contains no code. It is an alias that depends onsilo-sdk[mcp-server]at an exact matching version, so it installs precisely whatpip install "silo-sdk[mcp-server]"installs and nothing else. The MCP server continues to ship insidesilo-sdkas thesilo_sdk_mcppackage and thesilo-sdk-mcpconsole script; this is a discoverability alias, not a split. - Nothing changes for existing installs.
silo-sdk[mcp-server]remains the primary spelling and is what you want if you already depend on the SDK. Reach forsilo-sdk-mcponly if the server is what you came for and the SDK is incidental. - The two are pinned together with
==and released from the same commit, so they cannot skew: the version of the alias is always the version of the server it installs. The console script is declared only bysilo-sdk— two distributions claiming the same script name would make which one you get depend on install order.
Security¶
- Every dependency lower bound has been raised past the known vulnerabilities in the versions it used to admit. A
>=bound is a security statement, not only a compatibility one — the resolver may legitimately pick the floor in any environment that already pins that package, so a fresh install resolving to something current proves nothing about the range being published. Nothing here means an install of v0.14.0 was itself vulnerable: a defaultpip installresolved to current releases throughout, and the dependency audit that runs in CI passed against that resolution. What was wrong is what the package permitted, and only a consumer whose own environment held one of the older versions would have been affected. Each floor now sits at the first release clearing every advisory known against the range below it. requests>=2.32.5→>=2.33.0(GHSA-gc5v-m9x4-r6x2)urllib3>=2.6.3→>=2.7.0(GHSA-qccp-gfcp-xxvc, GHSA-mf9v-mfxr-j63j)python-dotenv>=1.0.1→>=1.2.2(GHSA-mf9w-mj56-hr94)cryptography>=46.0.5→>=50.0.0, in both thedecryptandlegacy-decryptextras — six advisories against the old range, the last of them fixed only in 50.0.0 (GHSA-g6cj-pr64-35w5, GHSA-jwv3-5hgf-82ww, GHSA-m2h6-j472-rp4c, GHSA-m959-cc7f-wv43, GHSA-p423-j2cm-9vmq, GHSA-537c-gmf6-5ccf)mcp[cli]>=1.0.0→>=1.28.1in themcp-serverextra (GHSA-9h52-p55h-vw2f, GHSA-jpw9-pfvf-9f58, GHSA-vj7q-gjh5-988w) — see Fixed below, because this bound was also broken for a non-security reason.- Action required (only if you pin transitively): these are floors, so an environment already on current releases is unaffected and needs nothing. An environment holding a pin below one of them will now fail at resolution time with an unsatisfiable-requirement error rather than installing quietly. That is the intended outcome — raise the pin.
certifi>=2024.07.04→>=2026.07.22. No advisory tracks this one, because a stale CA bundle is not a vulnerability in certifi's code — it is a trust store carrying roots it should no longer carry, which is exactly the risk a floor is supposed to keep out. The old bound admitted bundles still shipping the Baltimore CyberTrust Root, expired since 2025-05-12, and — less obviously — bundles that had re-admitted the GLOBALTRUST 2020 CA after removing it, which is only permanently absent from 2025.8.3 onward. The new floor is the first bundle with no expired roots and every distrust decision applied. It is deliberately left uncapped: an upper bound on a trust store would pin consumers to a snapshot of the web PKI, which is the failure this change exists to prevent.
Fixed¶
- The
mcp-serverextra could not install at its own declared minimums, and its lowest supported version crashed the HTTP transport.mcp[cli]>=1.0.0admitted releases with nomcp.server.transport_securitymodule — added in 1.10.0 — which the server imports unconditionally to configure DNS-rebinding protection. Any such release installed cleanly and then raisedModuleNotFoundErrorthe moment the server was started over HTTP or SSE. Separately, the extra declaredfastapi>=0.111.0alongsidemcp, and their transitive Starlette requirements were disjoint (<0.38against>=0.39), so installing the declared minimums failed outright with a resolution error. Both are corrected;uvicorn[standard]moves to>=0.31.1because that is what the newmcpfloor requires. pyyaml>=6.0.0→>=6.0.2in themcp-serverextra. 6.0.0 and 6.0.1 publish no wheels for Python 3.12 and newer, and their source distribution fails to build against current setuptools. 6.0.2 is the first release with wheels for every supported interpreter.
Removed¶
fastapiis no longer a dependency of themcp-serverextra. It was never imported. The MCP server is built onFastMCP, which constructs its own Starlette application, and no module in the distribution has ever referenced FastAPI. Installing the extra now pulls one fewer web framework and its transitive tree. No behavior changes; if your own code importedfastapiand relied on this extra to supply it, declare it directly.
[0.14.0] - 2026-09-09¶
A minor bump rather than 0.13.2: the silo-mcp → silo-sdk-mcp rename below is a
breaking change to a published interface.
Added¶
- New
LOG_EXTRACT_TIMEOUTconfig key (A8_LOG_EXTRACT_TIMEOUT) — a dedicated request timeout forLogExtractionAPI, separate from the SDK-wideREQUEST_TIMEOUT. It ships unset, andLogExtractionAPIfalls back to 600 seconds; see the behavior note under Changed, which depends on being able to tell "unset" from "deliberately set to 600". - New
MCP_FILE_ROOTenvironment variable for the MCP server — the directory (orPATH-style list of directories) thata8_upload_fileanda8_download_fileare allowed to read and write. Defaults to a dedicatedsilo_sdk_mcp_filesdirectory under the system temporary directory, created on demand. See Security below for why local file access is now confined at all. REQUEST_TIMEOUT,MAX_RETRIES, andRETRY_DELAYare now environment-templated inconfig/default.json, soA8_REQUEST_TIMEOUT/A8_MAX_RETRIES/A8_RETRY_DELAYtake effect without editing the config file. Previously the file shipped bare literals and those variables were silently ignored. All four timeout/retry variables are now documented in.env.example.scripts/create_context.py— create a single browsing context and print its launch URL. Unlikebulk_create_contexts.py, the destination URL is optional: omitting--urlopens the session's default start page, which is the usual way to start an ad-hoc Silo for Research session. One of--useror--orgis required.
Changed¶
- Python support: minimum raised to 3.11, and 3.14 added. Python 3.10 reaches end of life on 2026-10-31 and is no longer tested or supported;
requires-pythonis now>=3.11. Python 3.14 joins the tested matrix, which is now 3.11, 3.12, 3.13, and 3.14 — the full set of releases still receiving upstream security fixes. Raising the floor also lets the SDK rely on 3.11 language and standard-library features directly. - Action required (Python 3.10 users only): nothing already installed breaks — an existing 3.10 environment keeps working, and the
v0.13.1tag remains the last release that installs on 3.10. Installing this version or later on 3.10 fails cleanly at resolution time with aRequires-Python: >=3.11error rather than installing something broken, so pin tov0.13.1or move to Python 3.11 or newer. Note that Ubuntu 22.04 LTS ships Python 3.10 as its system interpreter — install a newer interpreter or build your virtual environment on one. LogExtractionAPInow defaults to a 600-second request timeout instead of the SDK-wide 30 seconds. The backend serializes extracts behind a per-extract lock with a 600-second TTL. A client that gave up at 30s abandoned a request still running server-side while the lock stayed held, so every retry failed withPermissionDenied: log.extract already in progressuntil the TTL expired — turning a slow success into a ten-minute outage. The client timeout is now pinned to that lock TTL, so anything that does time out has also released its lock and the retry is clean. Only log extraction is affected; the 30-secondREQUEST_TIMEOUTstill governs the sub-second admin and browsing calls that dominate the SDK. SetA8_LOG_EXTRACT_TIMEOUTto override.- The new default raises the extraction timeout but never lowers it. Anyone who had already set
REQUEST_TIMEOUTabove 600 to work around the old 30-second limit keeps that larger value — a fix for slow extracts that shortened them for the people most affected by the bug would be a regression in fixer's clothing. An explicitLOG_EXTRACT_TIMEOUTstill wins outright, including one belowREQUEST_TIMEOUT, because that is a deliberate choice rather than an inherited default. - Templated numeric config values are now cast to numbers and range-checked. Environment-variable substitution always produces strings —
"${A8_REQUEST_TIMEOUT:-30}"resolves to"30", not30— so making the timeout and retry keys templatable would otherwise have handed a string torequestsand brokenvalidate_harvester_config()'sisinstance(value, (int, float))check with a misleading "must be a positive number".REQUEST_TIMEOUT,LOG_EXTRACT_TIMEOUT,MAX_RETRIES,RETRY_DELAY, andSTATUS_CACHE_TTLare coerced after substitution and before defaults are applied, on both config-loading paths. A non-numeric value now raisesConfigurationErrornaming the key instead of failing later somewhere unrelated.DEFAULT_MAX_USESis deliberately excluded — no consumer reads it as a number and its string form is part of the tested contract. - Durations accept fractional values; counts do not.
REQUEST_TIMEOUT,LOG_EXTRACT_TIMEOUT,RETRY_DELAY, andSTATUS_CACHE_TTLare seconds, andrequestsis happy with2.5;MAX_RETRIESis a count, so a fractional value is rejected rather than truncated. Previously the same value was accepted or rejected depending only on how it arrived —{"RETRY_DELAY": 2.5}written into a config file worked whileA8_RETRY_DELAY=2.5did not, purely because substitution turns the latter into a string. - Out-of-range values are rejected up front. A zero or negative
REQUEST_TIMEOUT/LOG_EXTRACT_TIMEOUTfails every request before it is sent, so it is now aConfigurationError; the counts and delays reject negatives but still allow zero, which is a legitimate way to say "no retries", "no delay", or "no caching". The checks apply to literal values in a config file as well as templated ones, and a boolean is rejected outright rather than being quietly read as1. - CLI entry point and Python package renamed:
silo-mcp→silo-sdk-mcp,silo_mcp→silo_sdk_mcp. The console script, the server name advertised over the MCP protocol, the server key used in client configuration files, and the importable package are now all named after thesilo-sdkdistribution the server ships in. The package path was the last name that did not match, andsilo-mcpwas ambiguous enough to be worth retiring outright. - Action required: update the server key and
commandin any MCP client configuration (.mcp.json, Claude Desktop config,.codex/config.toml). Claude Code CLI users should re-register:claude mcp remove silo-mcp --scope userthenclaude mcp add silo-sdk-mcp --scope user -- <command>. - Action required (importers and
python -musers):python -m silo_mcpbecomespython -m silo_sdk_mcp, and anyfrom silo_mcp… import …becomesfrom silo_sdk_mcp… import …. There is no compatibility shim — the old package path is gone. - Action required (reinstall): re-run
pip install -e ".[mcp-server]"(or reinstall the wheel). Thesilo-sdk-mcpconsole script and, for editable installs, the generated import finder both bake the module path in at install time and do not update on agit pull— until you reinstall, the console script fails withModuleNotFoundError: No module named 'silo_mcp'. Note thatpython -m silo_sdk_mcprun from the repository root keeps working regardless, because the current directory is onsys.path; that masks the problem in local smoke tests, so reinstall rather than relying on it. - Action required (credentials file and Docker): the credentials file moves with the package, on the host as well as in the image —
silo_mcp/credentials.yaml→silo_sdk_mcp/credentials.yaml, and/app/silo_mcp/credentials.yaml→/app/silo_sdk_mcp/credentials.yaml. Because that file is gitignored, pulling this change deletes the tracked files around it but leaves your credentials file behind in an orphanedsilo_mcp/directory. Move it, then delete the leftover directory — it holds live API tokens. Do this before starting Compose: a bind mount whose host path is missing gets created as an empty directory rather than failing, so the container starts and then dies parsing a directory as YAML. - Logging namespace changed from
silo_mcptosilo_sdk_mcp. Any downstream logging configuration keyed on the old logger name silently stops matching — no error, just lost filtering or routing. - Tool names are unchanged — all 73 tools keep their
a8_prefix. - Development tooling: Ruff replaces black, isort, flake8, bandit and pylint. One tool now covers formatting, linting, import sorting, docstring style and security scanning, configured in one place (
[tool.ruff]inpyproject.toml) rather than across four files that could disagree. Nothing in the shipped package changes; this affects contributors only. Details worth knowing if you have a local checkout: - Coverage is preserved, not traded away. Ruff's
Srules are flake8-bandit — the same rule set bandit ran — andDruns under thepep257convention flake8-docstrings defaulted to, with the same rules disabled as before.E/W/F/B/C4cover flake8 and its usual plugins. The two jobs Ruff structurally cannot do, because it reads one file at a time, are still handled separately: mypy for type checking andpip-auditfor dependency CVEs. Pylint is simply gone — it ran with--exit-zero, so it never gated anything. - One formatting pass touched 26 files.
ruff formatis a black reimplementation but not byte-identical to black 26.3.1: it rejoins implicit string concatenations that fit on one line, and spaces binary operators inside f-strings. The changes are mechanical and were made in the same commit as the tool swap. - Line length behaves as before. The formatter wraps at 88 and the lint gate still fails at 100, matching what flake8 enforced — a long URL or string literal the formatter cannot split does not suddenly become an error.
- Lint and format now run over the whole tree in CI, not a path list — so the CI steps cannot drift from what the pre-commit hooks check.
examples/,eval/,config/andsetup_venv.pywere never gated before and came in clean, making the staged cleanup CONTRIBUTING.md had planned for them unnecessary. Two exclusions, both in[tool.ruff]:Documentation/archive/, and*.md— Ruff can format Python code blocks inside Markdown, which is a docs decision rather than a linter one. - Type annotations modernized to PEP 585/604 —
Dict[str, Any]is nowdict[str, Any],Optional[X]isX | None, and thetypingimports they needed are gone. Ruff'sUPrules flagged ~1,900 of these once the floor moved to 3.11; the rewrite is mechanical and lands in its own commit so the churn does not obscure the tool swap. The package shipspy.typed, so this is visible to consumers reading the annotations — but only as spelling, since both forms mean the same thing to a type checker and 3.11 is already the minimum. - Action required (contributors): re-run
pip install -e ".[dev]"andpre-commit install --install-hooks.black,isort,flake8,banditandpylintare no longer dev dependencies, and.flake8has been deleted. OrgManagementAPI.delete_org()now raises instead of returningFalse. The method advertisedTrueon success andFalseon failure, butFalsewas unreachable: the server answers a delete with either a confirmation payload or an error, and an error already raisedOrgManagementAPIError. TheFalsebranch could only be reached by a response shape the API does not produce, so in practice the method already either returnedTrueor raised — while its signature invited callers to writeif not api.delete_org(name):and handle a case that never arrives, and to not handle the exception that does. An unconfirmed response is now anOrgManagementAPIErrornaming the org and the result it got, rather than a log warning and a falsy return that is indistinguishable from a genuine refusal.- Action required (callers that branch on the return value): wrap the call in
try/except OrgManagementAPIErrorinstead of testing the result. Code that only checks truthiness keeps working unchanged, because the success return is stillTrue. Code that treatedFalseas "deletion failed" was never reached and was silently reporting nothing; the exception now carries the server's own refusal message — most often that the organization still contains users or sub-orgs. - The docstring also described a permanent hard delete. The server soft-deletes: it stamps an expiration on the organization and queues its removal. The organization is immediately unusable and there is no API to reverse it, so the practical effect is unchanged, but the previous wording described mechanics that are not what happens. The
a8_delete_orgMCP tool description carried the same two errors and has been corrected to match. devnow carries a.dev0version suffix while it is ahead of the last tag. Until nowdevreported the same__version__as the release it branched from, so apip install -efrom adevcheckout was indistinguishable from the tagged release despite differing behavior — which made "which SDK produced this result?" unanswerable during a support investigation.devwill be set to<next>.dev0immediately after each release and to the bare version at tag time.
Security¶
- The MCP file tools now confine local filesystem access to a configured root.
a8_upload_filetook any local path and sent its contents to the storage API;a8_download_filetook any local path and wrote bytes to it. Both paths come from the model, and this SDK exists to fetch web content and hand it to that model — so in the ordinary use case the path is influenced by pages the operator does not control. Text on a fetched page that talks the model intoa8_upload_file("/proc/self/environ")exfiltrates everyA8_*token the server process holds, and a download aimed at~/.ssh/authorized_keysor a shell rc file is a straight path to code execution on the host. Neither tool checked anything. - Both tools now resolve the requested path (following symlinks, so a link inside the root cannot smuggle one out) and reject anything that does not land inside a permitted root. A relative path anchors to the first root rather than to the server's working directory, which the client cannot see and which typically holds
.env. - The default root is a dedicated
silo_sdk_mcp_filesdirectory under the system temporary directory, created on demand. It is deliberately not the working directory and not the whole temp directory. SetMCP_FILE_ROOTto point somewhere real; it accepts aPATH-style list, so a read directory and a write directory can both be permitted without widening either. - Action required (existing MCP deployments): an upload or download naming a path outside the root now fails with a message saying which roots are permitted. If your workflow moves files through a specific directory, set
MCP_FILE_ROOTto it before upgrading. - The MCP HTTP transports now refuse to start when DNS-rebinding protection would be off. Binding to a non-loopback host without
MCP_ALLOWED_HOSTSpreviously logged a warning and started anyway, with host-header validation disabled — the configuration where any web page the operator visits can drive the server through the browser. The warning was the only thing standing between a copy-pasted--host 0.0.0.0and an unauthenticated local attacker, and warnings scroll past. The server now exits with a non-zero status and an explanation. An explicitly emptyMCP_ALLOWED_HOSTSis treated the same way, rather than as "allow everything". --credentialspointing at a directory or an empty file now exits instead of starting. A bind mount whose host path does not exist is created as an empty directory, so this was the failure mode of a mistyped Compose path: the server started, parsed nothing, and served no credentials while reporting itself healthy.- Response bodies are now actually redacted before being logged.
_redact_responsematched a single key name that only ever appears in requests, so on a response it was a no-op — every unexpected-shape error logged the server's reply verbatim, and API replies carry session identifiers and, on some paths, tokens. It now walks nested dictionaries and lists and masks values under any key containingtoken,password,secret,api_key,credential,private_key,session_idorauthorization, and the rendered form is truncated so a large reply cannot flood the log. This was a control that existed in name only; the name is now accurate. API_URLmust use HTTPS. Nothing checked the scheme, so anhttp://value — from a typo, a copied internal note, or an attacker-writable config file — silently sent every API token in cleartext. A non-HTTPSAPI_URLis now aConfigurationErrorat client construction. Loopback addresses (127.0.0.1,localhost,::1) are still allowed over HTTP, with a warning, because that is how local mock servers are used in testing.- Removed the
VERIFY_SSLconfig key. It appeared inconfig/default.json,config/production.jsonand the configuration reference, but no SDK code read it — certificate verification is unconditional on both the synchronous and theaiohttppaths, against thecertifitrust store. A documented switch that does nothing is worse than no switch: it invites someone to set it tofalse, conclude verification is off, and design around that. It has been deleted rather than wired up, and the configuration reference now states plainly that verification is not configurable. scripts/setup_a8_api_env.shwrites to.env, not~/.zprofile. It prompted for five API tokens and appended them to the user's login profile, which exports them into the environment of every process that user starts — where they leak through/proc, crash dumps and diagnostics — and which is created world-readable. It now writes the repository's gitignored.envat mode0600, via an atomic replace so an interrupted run cannot truncate a file full of credentials. Existingexport A8_*lines are recognized and replaced rather than duplicated. Tokens already sitting in a~/.zprofilefrom an earlier run should be removed by hand and rotated.scripts/run_postman_eval.shno longer passes tokens on the command line. Every account on the host can read the full argv of a running process out ofps, and CI runners log the command they invoked. The five tokens now travel in a short-lived0600environment file inside a randomly-named temporary directory, removed on exit.- Scrubbed captured production values from the Postman collection and
.env.example. A saved example response in the collection carried a real production hostname along with live-looking organization, bucket and task identifiers, and.env.examplecarried a real Microsoft Entra tenant and application ID. All are now placeholders. The internal script that regenerates those examples reads its SAML metadata URL from the environment instead of a hardcoded tenant URL. .dockerignoreexcludes the per-environment config files.config/qa.json,config/eng.jsonandconfig/demo.jsonare gitignored, so unlikedefault.jsonandproduction.jsonnobody reviews what a developer put in them. A local copy holding real tokens would otherwise be copied into the build context and baked into a layer that cannot be rotated.- Test files are no longer exempt from the TLS lint rules. The bandit configuration Ruff replaced excluded the whole test tree, and carrying that over wholesale also switched off
S501(a request with certificate verification disabled) andS323(an unverified SSL context) — the two rules that catch a "just for the test" TLS bypass, which is exactly where one gets written. The per-file exemption is now an enumerated list of the rules that genuinely are noise in a test suite (asserts, fake tokens, temp paths), and the TLS rules gate the tests like everything else. seccureis now upper-bounded (>=0.5.0,<1.0) in the optionallegacy-decryptextra, matching every other dependency. It has had one release since 2014, so a surprise1.0would most likely be a new maintainer or a name transfer rather than an upgrade — not something an unbounded>=on a crypto package should resolve to silently.- Docker images no longer bake in the local credentials file.
Dockerfile.mcpcopies the whole working tree, and.dockerignorecarried no entry forsilo_sdk_mcp/credentials.yaml— so on any machine where the server had been run locally,docker buildcopied live API tokens into an image layer..gitignoredoes not apply to Docker builds. The server reads its credentials from a read-only bind mount at runtime, so the file has no reason to be inside the image at all; it is now excluded by basename, so the exclusion holds regardless of where the package sits. Editor and agent working directories and local scratch output were being copied in as well, and are now excluded too — that alone takes the image from 1.33 GB to 796 MB. The same exclusions were added toMANIFEST.in, since an sdist is likewise built from the working tree rather than from git. - Action required: rebuild any image built before this change. An image layer cannot be scrubbed after the fact, so if such an image was pushed to a registry or otherwise shared, treat the tokens in it as exposed and rotate them.
Fixed¶
- The log-extraction timeout clamp never ran. The guarantee described under Changed — that the new 600-second default raises an existing larger
REQUEST_TIMEOUTbut never lowers it — was keyed onLOG_EXTRACT_TIMEOUTbeing absent from the configuration. Butconfig/default.jsonshipped"${A8_LOG_EXTRACT_TIMEOUT:-600}", which resolves to600whether or not the environment variable is set, so the key was always present and always looked deliberate. Anyone who had raisedREQUEST_TIMEOUTabove 600 to work around the old 30-second limit silently had their extraction timeout cut to 600 by the release that was supposed to protect it. The shipped default is now empty, and a blank value is treated as unset — so the clamp works, and an explicit600is once again distinguishable from no setting at all. - A blank templated numeric config value is now treated as unset rather than as an error.
"${A8_LOG_EXTRACT_TIMEOUT:-}"with the variable unset resolves to an empty string, which the numeric coercion rejected withLOG_EXTRACT_TIMEOUT must be a number, got ''. Because every CLI script underscripts/loads the configuration at import time, that turned--helpinto a traceback on a machine with noA8_*variables exported. Blank values for any of the numeric keys are now dropped, so consumers see a missing key and apply their own default. walk_org_tree()andprint_org_tree()no longer skip subtrees silently. Both swallowed every exception while descending, so an organization the caller lacks rights on — or a transient API error — produced a quietly truncated tree that looks exactly like a complete one. Skipping the branch is still the right behavior, since one inaccessible organization should not abort a whole-tree walk, but it now logs a warning naming the branch and the reason, so a short answer is visibly short. (The previous code carried a# nosecmarker, which has been inert since bandit was replaced by Ruff'sSrules — Ruff readsnoqa.)scripts/run_postman_eval.shignoredA8_CONFIG_ENVand always ran against production. The variable was documented in the script's own usage text, but the Production environment file was hardcoded — soA8_CONFIG_ENV=eng ./scripts/run_postman_eval.shran the full collection, creates and deletes included, against production while reporting otherwise. The variable now selects between the production, engineering and QA environment files, and an unrecognized value is an error rather than a silent fall-through. The default is also nowengrather thanprod: a run that creates and deletes real users and orgs should not target production because someone forgot to set a variable.- The 100-character line-length gate was never enforced.
E501was listed in Ruff'signore, andignoretakes precedence overselect— so themax-line-length = 100setting alongside it, and the changelog note in this release saying the lint gate still fails at 100, described a check that could not fire.E501has been removed from the ignore list; the tree already complied. - The MCP credentials template was missing from installed packages. Neither the wheel nor the sdist contained
silo_sdk_mcp/credentials.yaml.exampleorsilo_sdk_mcp/README.md, so the documented setup step of copying the template out of the installed package only ever worked from a git clone. Both files now ship, listed explicitly as package data rather than relying onMANIFEST.into reach the wheel. Dockerfile.mcpinstalled the package before copying the source into the image. An editable install resolves its package list at install time, so installing against a directory holding onlypyproject.tomlproduced an import finder with an empty mapping, and the later source copy could not retroactively fix it. Inside the image thesilo-sdk-mcpconsole script failed withModuleNotFoundErrorand imports only worked from/app. This was masked by the container's ownCMD, which usespython -mand therefore picks the package up from the working directory regardless. The source is now copied before the install.- Numeric values in
credentials.yamlwere not cast on the MCP HTTP-transport path. The per-client configuration loader ran environment-variable substitution but not the numeric coercion the SDK's own config loader applies, so acredentials.yamlwritten in the documented"${A8_REQUEST_TIMEOUT:-30}"style handedrequeststhe string"30"and every call from an HTTP-transport client failed with aTypeErrorfrom inside the HTTP layer. Both loading paths now share one coercion step, so they cannot drift apart again. silo-sdk-mcp --credentialsdefaulted to a path relative to the current directory. The default wassilo_sdk_mcp/credentials.yaml, resolved against wherever the shell happened to be — but the setup instructions have users copy the template out of the installed package, so the file they end up editing sits next to the module. Starting the server from anywhere other than a repository root therefore failed to find a credentials file that was exactly where the docs said to put it. The default is now derived from the package location, which resolves to the same path as before in both documented layouts (a checkout run from its root, and/appin the image).- The Compose file's
MCP_ALLOWED_HOSTSdefault ignoredMCP_BIND_HOST. Binding the container to a non-loopback address left the DNS-rebinding allow-list pinned to127.0.0.1:8080, so every request to the address the server was actually reachable on was rejected — with a host-header error that gives no hint the bind address is involved. The default now derives fromMCP_BIND_HOST, and an explicitMCP_ALLOWED_HOSTSstill overrides it. - The remote-deployment documentation described the superseded SSE transport as the default. The setup and overview pages showed
--transport sse, the/sseendpoint in every reverse-proxy example, and anMCP_CREDENTIALS_FILEenvironment variable the server does not read; the published port example also bound0.0.0.0. They now documentstreamable-httpand its/mcpendpoint, bind the published port to loopback so a reverse proxy is the only route in, and note whereMCP_ALLOWED_HOSTSmust list the public hostname.ssestill works and is still documented as the fallback for older clients. - The same correction reached the packaged
silo_sdk_mcp/README.mdand the CLI's own--helptext, which still described--portand--credentialsas SSE-only and showed a/sseclient URL. A reader who took the transport advice from the published docs and the endpoint from the bundled README would have configured a client that could not connect. The security note now also states that--hostdefaults to0.0.0.0— every interface — and points at--host 127.0.0.1behind a TLS-terminating proxy. - The configuration reference omitted seven shipped config keys.
TOP_ORGappeared only as a script-scoped environment variable despite being required bya8_validate_configand probed bya8_health_check, andENABLE_STATUS_CACHE,STATUS_CACHE_TTL,RATE_LIMIT,DEFAULT_EGRESS_INFO,DEFAULT_POLICYandENABLE_REQUEST_LOGGINGwere absent entirely — all six ship inconfig/default.json, so readers met them for the first time in a file the docs never explained. TheA8_REQUEST_TIMEOUT/A8_MAX_RETRIES/A8_RETRY_DELAYmappings were missing from the environment-variable table as well. All are now documented, along with a note thatORG_VANITY_URLis not the API org name —TOP_ORGis. Two are documented as inert rather than as working settings: nothing readsENABLE_REQUEST_LOGGING, andRATE_LIMITis shape-validated but never enforced, so the SDK does not throttle on your behalf. - The remote-deployment documentation showed a two-client
credentials.yamlthat will not start. It described the server as loading the first client entry and sharing those credentials across all authenticated sessions; in fact the server counts the entries and exits rather than serve one client's tokens to another. Anyone following the example got an immediate startup failure with no hint that the file itself was the problem. The example is now single-client, and the architecture diagram shows one instance per client behind the proxy — which was always the supported shape for serving more than one.
Removed¶
silo_sdk_mcp.__version__andconfig.__version__. Both were sub-package version numbers with no consumers, and both had silently drifted from the distribution version (the MCP sub-package, then namedsilo_mcp, sat at0.10.3through four releases;configat0.8.2since April).silo_sdk.__version__is the single source of truth — asDEFAULT_USER_AGENTalready assumes. A second number that can drift is worse than no number.
[0.13.1] - 2026-07-17¶
Changed¶
- Raised MCP tool test coverage from 95% to 100% by adding tests for previously-untested error-handling and edge-case branches across every
silo_mcp/tools/*module. raise_mcp_error(), the shared exception-translation helper used by all MCP tools, is now type-annotated as never returning normally, and the defensiveraisestatements that used to follow it (which could never execute) have been removed.- Fixed a misleading comment on the log-extraction retry loop's exhaustion guard: passing a negative
max_retriestoa8_extract_logsskips the extraction attempt entirely and raises a generic "Exhausted retries" error rather than being unreachable as previously documented; this is now covered by a test and documented accurately in the code.
[0.13.0] - 2026-07-15¶
Security¶
- SAML metadata parsing hardened against SSRF and XML entity-expansion attacks — found in a security review of
parse_saml_metadata()/fetch_metadata_url()(used bya8_parse_saml_metadataand the SSO-import CLI command): fetch_metadata_url()now only acceptshttp/httpsURLs and rejects loopback, private, link-local, multicast, unspecified, and reserved IPv4/IPv6 destinations. Every DNS answer and redirect target is validated before a request is sent; requests are pinned to a validated address and the connected peer is checked to prevent DNS rebinding. Non-http(s)schemes still raise a clearValueErrorbefore any fetch is attempted.- MCP downloads that omit
output_pathnow sanitize the storage-provided filename and place it inside a uniquely-created temporary directory, preventing absolute paths or traversal segments from escaping the temporary download location. parse_saml_metadata()now parses XML withdefusedxmlinstead of the standard library parser, which rejects entity-expansion ("billion laughs") payloads and external entity references (XXE) up front instead of resolving them.defusedxmlis now a required dependency.parse_saml_metadata()no longer silently uses the firstEntityDescriptorwhen given a metadata bundle containing more than one (e.g. a federation aggregate). It now raises a clear error asking the caller to disambiguate, unless the new optionalentity_idparameter is supplied to select which entity to use.- Breaking change: tampered or corrupted encrypted log entries were silently returned as if successfully decrypted.
decrypt_log_entry()caught every decryption error — including AES-GCMInvalidTag, the exact signal that a ciphertext was tampered with or corrupted — and returned a dict with just anerrorfield merged into the entry's normal wrapper fields (create_ts,seq_id, ...). That result was indistinguishable from a genuinely decrypted row to any caller that didn't inspect every entry for anerrorkey, anddecrypt_logs()appended it straight into the "successfully decrypted" list. Decryption/authentication failures are no longer swallowed: decrypt_log_entry()now raises the newLogDecryptionErroron any decryption or authentication failure instead of returning a pseudo-success dict.decrypt_logs()raises the same error by default as soon as a failing entry is encountered (stopping the batch, so partial results are never silently mixed with tampered data). Pass the newtrack_failed=Trueto instead collect failed entries into a separate list and keep processing the rest of the batch — mirroring the existingtrack_missingparameter for entries with no matching key.- Failed entries are never merged into, or returned alongside, successfully decrypted entries.
- Callers that previously checked decrypted entries for an
errorkey should instead catchLogDecryptionError(or passtrack_failed=Trueand inspect the returned failed-entries list). standard_decrypt_chunked()could leave unauthenticated plaintext in the output stream after a failed decryption. AES-GCM only validates the authentication tag insidefinalize(), which runs after plaintext chunks have already been written viaupdate()— contrary to the function's own docstring, which incorrectly claimed output was "buffered internally and written only after the auth tag is validated." On authentication failure, the function now makes a best-effort attempt to truncate any unauthenticated bytes back out of the output stream, and the docstring has been corrected to describe the real behavior and the temp-file-and-move-on-success pattern callers writing to a persistent destination should use.decrypt_video_file()now removes its plaintext temporary file after every failed decrypt, write, or final move.- CSV log export had no formula-injection sanitization. Untrusted log field values (URLs, POST data, clipboard contents, query strings, domains, ...) starting with
=,+,-,@, a tab, or a carriage return could execute as a spreadsheet formula (e.g.=HYPERLINK(...),=IMPORTXML(...)) when the exported CSV is opened in Excel or Google Sheets, per the OWASP CSV Injection advisory.LogExtractionAPI.export_logs_to_file(format_type="csv")now prefixes any such value with a single quote so spreadsheet applications render it as literal text.
Fixed¶
- Key store round trip was broken.
add_key_to_store()base64-encodes PEM text before writing it to thepvtkey.txtstore (necessary since a multi-line PEM value can't be represented on the store's singlename=valueline), butload_private_keys()never decoded it back — so the documentedgenerate_key_pair()→add_key_to_store()→load_private_keys()→ decrypt workflow hard-failed with aValueErrorinsideload_pem_private_key().load_private_keys()now automatically detects and decodes base64-encoded PEM values written byadd_key_to_store(); Legacy passphrases,.pemfile paths, and hand-written raw PEM values are left untouched. - ENC log schema listed the wrong field name. The
ENCCSV schema inlog_schemas.pylistedenc_algorithm, but the Authentic8 API documentation's Extract Log response example confirms the actual field returned alongside encrypted log entries isencryption_type(e.g."encryption_type": "Legacy"). With the wrong name, CSV export (extrasaction="ignore") silently dropped the field, and a later CSV-to-decrypt run would default every entry toLegacyregardless of its actual encryption type. Corrected toencryption_type. - HTTP retries could re-execute non-idempotent requests. The shared
Retryadapter allowedPOSTon 429/500/502/503/504, so a transient error after the server had already applied a mutating command (e.g.adduser,reset_pin) could cause the client to retry it, risking a duplicate side effect.allowed_methodsis now restricted to idempotent verbs only (GET,HEAD,PUT,DELETE,OPTIONS). batch_requestreturned the injectedsetauthacknowledgment as a false successful result. The ack (always index 0) was only stripped when the response had more than one element, so a single-command batch whose response contained only the ack — e.g. a command rejected at the auth phase — returned that ack disguised as the command's result. The ack is now stripped unconditionally.batch_delete_usersalso now applies the same "user not found" soft-success translationdelete_user()already had, so the batch and single delete paths present a consistent result contract.modify_fileposted directly to the base URL, missing theapi/path segment, instead of routing through the shared request helper like every other command.upload_file/download_filebypassed the shared HTTP session entirely (via barerequests.post), skipping session-level retry/proxy/User-Agent configuration.get_file_infoperformed a full-bucketfindfilesscan and filtered client-side instead of a targeted,file_id-scoped lookup. All three now use the shared request path/session. A failed upload or download (sync and chunked-async) also no longer leaves a truncated partial file behind — the partial file is now cleaned up on error.- Config loading did not treat a present-but-empty environment variable as unset.
${VAR:-default}-style substitution only fell back to the default when a variable was entirely absent, so an explicitly emptyA8_*value (e.g.A8_API_URL="") resolved to an empty string instead of the documented bash-style fallback, breaking every request with a blank base URL.config/production.json's fallbackAPI_URLalso embedded a redundant/api/(producing a double/api/and 404s), andConfigBuilderbypassed.envfile loading entirely, unlikeload_config(). A shell/process-levelA8_CONFIG_ENVnow also reliably takes precedence over a conflicting value merely defined in.env. - Bulk async harvest raised
KeyErroron a per-taskurlkey and ignored each task's ownegress_infoin favor of the bulk-level default, making it impossible to mix asset tasks (which require a datacenter location) with non-asset tasks in one call. Bothurlandurlsper-task keys are now accepted, and per-taskegress_infois honored. The async and sync harvest methods' return shapes were also unified, and egress validation now rejects connectivity+protocol combinations no single city actually supports together (e.g.isp+tor) instead of validating each dimension independently. create_context's inner URL-parsingValidationErrorwas caught by its own generic exception handler and double-wrapped, producing a confusing "Failed to parse URL: Invalid URL format: ..." message instead of the clean one.- MCP
a8_health_checkreported stale tool-capability counts (drifted from the actual registered tool list) and always reported0latency on not-found/error probes instead of real elapsed time.a8_debug_payload's docstring example showed the wrong endpoint (/cmd/instead of/api/).a8_create_org's already-exists fallback looked up the existing org by bare name, which can resolve to the wrong org if the name collides elsewhere in the hierarchy — it now scopes the lookup to the full parent path when known. The destructivea8_delete_org/a8_delete_partner_sso_configtools now reject a blank/whitespace-onlyorg_namebefore making any network call. - Usage-report functions silently zeroed or dropped rows when a per-org/per-user session-report fetch failed, making a fetch failure indistinguishable from confirmed zero activity. Failures are now logged at
WARNING(matching the existing org-traversal contract) and the affected row is markedpartial: Trueinstead — a partial row is never filtered out byinclude_inactive=False, so a fetch failure can no longer masquerade as a genuinely idle user.OrgManagementAPI.get_session_report()also now strictly validates the documentedMM-DD-YYYYdate format (previously a loose length/dash-count check silently accepted ISOYYYY-MM-DDdates and non-zero-padded values). - Two of the SDK's seven independent org-tree traversal implementations had no cycle guard at all (
get_org_usage_report,OrgManagementAPI.get_org_tree), unlike their siblings — a malformed or cyclicget_org_childrenresponse could produce duplicate rows. All org-tree traversals (list_users_recursive,get_org_usage_report,get_user_usage_report,get_org_tree, and themove_users.py/find_user_orgs.pyscripts) now share a single, consistently cycle-guarded and deduplicated traversal implementation. a8_list_users_recursivewas missing its own parameter-validation error handling, so an invalidmax_depthsurfaced as a generic "API request failed" error instead of a clear "Invalid parameters" message.
Changed¶
- MCP tool error messages are more specific and actionable. All MCP tool functions now route SDK exceptions through a shared error-translation helper instead of each hand-rolling its own conversion — rate-limit, authentication, and not-found errors get specific, actionable hints (e.g. "Use a8_find_user to search by partial email or name") instead of a generic "API request failed" message. Exception types raised to callers (
ValueError/RuntimeError) are unchanged. modify_user/list_usersnow raise on an unexpected API response shape instead of silently returning a sentinel value ({"status": "unknown", ...}/[]), so a broken or reshaped response is no longer indistinguishable from a genuinely empty result.
[0.12.0] - 2026-07-15¶
Fixed¶
- Org-name collision could silently drop or misattribute data — a bare org name is not guaranteed unique within a hierarchy (the same leaf name, e.g. "Research", can recur under many unrelated parents), and the platform rejects a bare name that collides elsewhere as ambiguous. This affected several SDK code paths, each now fixed to track and use the full path (e.g.
"MyOrg/SubOrg") instead of the bare name: list_users_recursive()/get_user_usage_report()(MCP:a8_list_users_recursive/a8_user_usage_report) — previously deduped visited sub-orgs by bare name (silently dropping a colliding org's entire subtree during traversal), and separately fetched each org's users by bare name (failing the fetch for a colliding org even once traversal found it correctly). Both issues are fixed; theorg/org_namefield on returned rows is now the full path for any sub-org.get_org_usage_report()— fetched each org's info by bare name; now uses the full path. Theorg_namefield on returned rows is now the full path for any sub-org.OrgManagementAPI.get_org_tree()/a8_get_org_tree()— fetched each node'sorg_id/user count by bare name; now uses the full path internally (the nested tree's ownorg_namefield stays bare, since nesting already gives callers path context).silo_sdk.utils.org.walk_org_tree()— deduped visited orgs by bare name and fetched children by bare name; now tracks full paths (dedup is now cycle-safe via ancestor-chain checking rather than a global visited set, so it also tolerates a malformed/self-referencing API response without dropping unrelated same-named orgs). Yielded records'org_nameis now the full path for any non-root org.scripts/move_users.py/scripts/find_user_orgs.py— built their own org tree with no deduplication at all and searched/listed users by bare name; both now track full paths with deduplication.- Silent partial results on sub-org lookup failure — a failed org-children lookup while traversing a hierarchy in
list_users_recursive()/get_user_usage_report()/get_org_usage_report()previously logged only atDEBUGlevel, giving no signal that the returned data was incomplete. These failures are now logged atWARNING, and affected functions' docstrings now document that results can be incomplete without raising an exception. A related regression from this same fix cycle — narrowing exception handling enough that a malformed (non-get_org_children) failure while processing one subtree could crash the entire call instead of degrading just that subtree — is also fixed. a8_get_org_children(recursive=True)droppedorg_id— the flattening step only copiedorg_name/current_users/depthfrom each tree node, silently omitting theorg_idfieldget_org_tree()provides. Now included.
Added¶
org_idon everyget_org_tree()node —a8_get_org_tree()/OrgManagementAPI.get_org_tree()now include a stableorg_idper node, in addition toorg_nameandcurrent_users.org_idis unaffected by bare-name collisions between orgs elsewhere in the tree.
[0.11.0] - 2026-07-09¶
Fixed¶
- Corrected egress location connectivity data based on updated provisioning information — several locations' recorded connectivity types (datacenter/ISP/fixed-wireless/wireless-carrier) were out of date.
create_context(browsing) now validates thecategoriesparameter's structure and values; previously invalidcategorieswere silently accepted and forwarded to the API.
Changed¶
- Retired the Lebanon egress location — no longer accepted as a valid
egress_region/egress_info.namevalue. - Removed the
EGRESS_LOCATIONSmodule-level constant fromsilo_sdkandsilo_sdk.browsing. UseBrowsingAPI.get_egress_locations()instead, which returns the same data and stays current as locations change.
Added¶
- Connectivity and protocol (
direct/tor) selections are now validated against what is actually available at the requested egress location, for both browsing contexts and harvest tasks (video/single/visual task types). Asset harvest tasks are unaffected — they keep their existing fixed 6-location validation.
[0.10.3] - 2026-06-09¶
Fixed¶
- Corrected the ad-blocking policy type name to
ad_blockwithenable/disablevalues (previously documented asadblockwithblock/allow, which the API rejects). - Corrected the domain blocklist policy type name to
domain_block(previouslydomain_deny) across SDK docstrings, MCP tool help, and the browsing reference docs. - The default
User-Agentheader now always reflects the installed package version. It is derived from a single source (silo_sdk.__version__) instead of being hardcoded in multiple files, where it had drifted out of date (some paths reportedsilo-sdk/0.10.0).
Changed¶
- Clarified the
browser_chrome: seamlessconstraint: it applies to non-catchall users. Catchall users are always shown the minimal ribbon, regardless of policy. (Previously framed as "authenticated users only.")
Added¶
- Documented the optional
browser_profilefieldstimezone(IANA name) andlanguages(Accept-Language string), in addition toosandbrowser.
[0.10.2] - 2026-05-07¶
Added¶
- Setup script (
setup_venv.py --dev) now verifiesmcpandsilo_mcpimports after install and prints a hint if the[mcp-server]extra is missing.
Security¶
- MCP server (SSE and streamable-HTTP transports) now enforces single-client-per-process configuration with Bearer token authentication: the server refuses to start if
silo_mcp/credentials.yamllists more than one client. Every request is validated against the configured Bearer token using a constant-time comparison; a missing, malformed, or mismatched token gets HTTP 401.
Fixed¶
pyproject.tomldependency pins corrected formcpandfastapiextras.
[0.10.1] - 2026-05-05¶
Changed¶
- Package rename:
mcp_server→silo_mcp— the MCP server package is nowsilo_mcp/. The CLI entry point issilo-mcp(wasauthentic8-mcp). Install withpip install "silo-sdk[mcp-server]". - Packaging fixes: wheel now correctly includes
silo_mcp/and all sub-packages.
Fixed¶
- Replaced
aiohttpwithhttpxfor async HTTP to resolve a dependency conflict. - Windows compatibility fix for
cleanup.pyand test path handling.
[0.10.0] - 2026-05-05¶
Added¶
- Egress location details —
get_egress_locations(include_details=True)returns enriched per-city metadata includingconnectivity,availability, andprotocolinstead of plain name strings. Backward-compatible: default remainsinclude_details=False. EGRESS_LOCATION_DETAILS— new static dict insilo_sdk/browsing/isolation_api.pymapping all 50 egress cities to their supported connectivity types, availability modes, and protocol options. Confirmed against Harvester API docs (April 2026) and live API testing.- MCP
a8_list_egress_locations— now acceptsinclude_details: bool = True(enriched output by default for LLM callers). Existing callers usinginclude_details=Falseget the prior flat-string behavior. - TOR protocol per location —
protocolfield populated for all cities: Sydney (["direct", "tor"]), Moncks Corner SC (["tor"]only), all others (["direct"]). Confirmed via live API testing.
Fixed¶
- Availability values corrected —
VALID_AVAILABILITY_TYPESinegress_validation.pychanged from{"private", "shared"}to{"private", "public"}per the Harvester API documentation (April 2026, p.4)."shared"was never a valid backend value; any caller passingavailability="shared"would have silently received no egress results from the backend.EGRESS_LOCATION_DETAILS,CategoriesTypedDict, MCP docstrings, and all tests updated to use"public". - TOR egress city correction — Sydney supports both
"direct"and"tor"protocols; Moncks Corner SC supports"tor"only (not"direct"). Prior documentation stated both cities supported both protocols. protocolfield not exposed —get_egress_locations(include_details=True)previously extractedconnectivityandavailabilityfromEGRESS_LOCATION_DETAILSbut silently droppedprotocol. All three fields are now returned.
Changed¶
a8_list_egress_locationsMCP tool default changed from returning flat strings to returning enriched dicts (include_details=True). Callers that need flat strings must now passinclude_details=Falseexplicitly.
[0.9.1] - 2026-04-29¶
Added¶
- MCP server expanded from 59 to 73 tools across 9 modules
- Centralized error enrichment (
silo_mcp/errors.py) with actionable lookup hints and automatic rate-limit retry - Diagnostic tools:
a8_validate_config,a8_get_api_info,a8_debug_payload; enhanceda8_health_checkwith per-token latency and capabilities - Compound workflow tools:
a8_harvest_url,a8_create_browsing_url,a8_org_summary,a8_stream_logs,a8_export_logs_csv - Resilience:
fallback_per_typeina8_extract_logs, idempotency hints ina8_add_user/a8_create_org, optionalbucket_idina8_upload_file - Input sanitization via
strip_str()applied to all MCP tool string parameters - Mermaid diagram support in MkDocs; replaced ASCII architecture diagrams in MCP docs
- Docker credential delivery documentation: three methods (--env-file, -e flags, volume mount)
[0.9.0] - 2026-04-29¶
Added¶
- Schema validation script —
scripts/validate_log_schemas.pyvalidatesLOG_TYPE_FIELDSagainst real extracted log data; reports extra fields, empty types, and coverage summary; exit code 1 when gaps found (CI-friendly) - Encrypted log example —
examples/decrypt_logs.pydemonstrates the full ENC log extract-then-decrypt workflow with per-type breakdown and optional JSON output - Private key template —
examples/pvtkey.txt.exampledocuments thepvtkey.txtformat for both Legacy (seccure passphrase) and Standard (ECIES/EC PEM) key types - Test coverage — 137 new unit tests across 4 test files:
tests/test_extract_all_script.py(16 tests) — extract-all script retry logic and CLItests/test_log_stats.py(66 tests) — all 11 analysis functions, formatters, and file loadingtests/test_mcp_logging_tools.py(27 tests) — MCP retry-on-lock behavior and exception handlingtests/test_export_logs_csv.py(28 tests) — CSV enhancement flags and resume state- Log parsing utilities — New
silo_sdk/logging/log_utils.pymodule with JSON field parsing (parse_json_field,parse_all_json_fields), header extraction (extract_user_agents,extract_content_types), egress hierarchy parsing (parse_egress_hierarchy), type normalization (normalize_log_type,normalize_log_types), and CSV flattening helpers (expand_json_fields,flatten_nested_dicts) - Multi-type extraction script —
scripts/extract_all_logs.pyextracts multiple log types sequentially with configurable pauses and automatic retry on backend lock contention - Log analytics script —
scripts/log_stats.pywith 11 built-in analyses (domains, auth, users, egress, transfers, harvest, apps, nexus, sessions, bypasses, user_agents) and table/json/csv output formats - MCP retry-on-lock —
a8_extract_logsgainsretry_on_lock,retry_delay, andmax_retriesparameters for automatic lock contention handling - CSV export enhancements —
scripts/export_logs.pygains--expand-json,--expand-nested, and--parse-egressflags for richer CSV output with dot-notation column expansion - NEXUS log type — Added
NEXUStoVALID_LOG_TYPESandLOG_TYPE_FIELDSfor Nexus AI conversation events (fields:conversation_id,message_type,org_name,toolbox_name, etc.) - ENC wrapper schema — Added
ENCtoLOG_TYPE_FIELDSwith wrapper fields (create_ts,enc,enc_algorithm,key_name,org_id,seq_id,session_id,type) - DVR session recording how-to — New documentation page (
docs/how-to/dvr-recordings.md) explaining DVR recording discovery, download, and decryption workflow via FileAPI - 2 new MCP tools (59 total, up from 55):
a8_org_usage_report(Organization Management) — Usage report across an org hierarchy showing provisioned users, active users, session counts, total/avg/longest/shortest session times per org. Params:org_name,start_date,end_date,max_depth(default 5),active_only(defaultFalse).a8_user_usage_report(Organization Management) — Per-user usage report across an org hierarchy. Params:org_name,start_date,end_date,max_depth(default 5),include_suspended(defaultTrue),active_only(defaultFalse). Returns flat list with org, username, session count, total/avg/longest/shortest session times.- 2 new CLI scripts:
scripts/org_usage_report.py— Generate org-level session usage reports with tree-style hierarchy display, CSV export, and active-only filteringscripts/user_usage_report.py— Generate per-user session usage reports with CSV export and filtering options- Management utility functions in
silo_sdk/utils/management_utils.py: get_org_usage_report()— Collect session statistics across an org hierarchyget_user_usage_report()— Collect per-user session statistics across an org hierarchy
Changed¶
- Log type field schemas synced with
logtype_headers.iniv1.0.4 — Updated 13 log type schemas with new fields: A8SS (+6), ADMIN_AUDIT (+3), CASE_MANAGER (+5), EVENT (+1), EXPLOIT (+6), HARVEST (+1), ISOLATE_BYPASS (+2), PRINT (+2), SESSION (+7), SMS (+7), TRAFFICMAN (+8), TRANSLATION (+1), URL (+5) VALID_LOG_TYPEScount — 25 → 26 (added NEXUS)LOG_TYPE_FIELDScount — 24 → 26 (added NEXUS and ENC)get_session_report()extended — Added optionalorg_id,user_id, andhierarchyparameters.org_id/org_nameare mutually exclusive;user_id/usernameare mutually exclusive.hierarchyparam enables subtree reporting (undocumented API feature).- MCP
or Nonenormalization — All optional string parameters insilo_mcp/tools/orgs.pynow useparam or Noneto prevent empty-string validation errors when AI assistants pass""for omitted optional params. a8_download_fileanda8_download_harvest_resultMCP tools —output_pathparameter is now optional. When omitted:a8_download_filesaves to temp directory using the original filename from Silo storage metadata (falls back to file ID if metadata unavailable)a8_download_harvest_resultsaves to temp directory asharvest_<task_id>.zip- Both tools add numeric suffixes (
_1,_2, etc.) to avoid overwriting existing files
[0.8.8] - 2026-04-13¶
Added¶
- 5 new MCP tools (55 total, up from 50):
a8_list_users_recursive(User Management) — Recursively lists users across an org and all its sub-orgs. Params:org,max_depth(default 5),include_suspended(defaultTrue). Returns a flat list withorg,username,email,given_name,surname,is_suspended,last_authorized_ts.a8_get_org_tree(Organization Management) — Returns the full org hierarchy as a nested dict. Params:org_name,max_depth(default 5). Returns{"org_name", "current_users", "children": [...]}.a8_resolve_org(Organization Management) — Resolves an ambiguous org name to its full canonical path. Params:org_name. Returns{"org_name", "org_id", "parent_org_name", "candidates": [...]}.candidatesis populated only when the name is ambiguous.a8_batch_suspend_users(Batch Requests) — Suspends multiple users in a single API round-trip. Params:usernames(list of str). Returns per-username result dicts.a8_batch_delete_users(Batch Requests) — Deletes multiple users in a single API round-trip. Params:usernames(list of str). Returns per-username result dicts.a8_upload_filecontentparameter — Accepts an optionalcontent: stras an alternative tofile_path. Provide exactly one offile_pathorcontent; providing neither or both raisesValueError.run-mcp.shnow appends stderr to/tmp/mcp-server.logfor post-disconnect diagnosis.
[0.8.7] - 2026-04-01¶
Fixed¶
org.update— API returns flat dict; SDK expected list → now checks dict firstdelete_partner_sso_config— API returns{"deleted":1,"status":1}dict; SDK expected int → fixed type checkget_session_report— API error responses embedded inresultdict were silently returned as success → now raisesOrgManagementAPIErrordelete_harvest_task— result extracted from wrong response index → fixed to use_extract_api_resultupload_file— omittedpathparam when value was"/"→ now always sent- Postman FLOW download requests — three FLOW requests (
Download File,Download Log File,Download Output File) used JSON command-array body onPOST /getfile/; confirmed via live test that/getfile/accepts multipart/form-data only (id+authfields); all three corrected - Postman harvest task wire format —
Create Asset Collection TaskandFLOW — Create Visual Harvest Taskhadtask_type/urlsat command level and params as dicts; corrected torequest_typeinsidetask_paramswith{name, value}array format docs/maintenance/api-recommendations.md— correctedsession_reportwire command fromorg.session_reporttosession_report; documented thatinclude_childrenis silently ignored and:hierarchyis the real undocumented param
Added¶
BrowsingAPI.update_context()— modify an existing browsing context (browse_context_id,context_data,max_uses,expires,name,enabled); registered in command registry; exposed asa8_update_contextMCP toolextract_logs()include_suborgsparameter — optionalbool; whenTrueincludes log records from child organizations; omitted from request when not providedfind_harvest_task()finishedparameter — optionalbool; whenTruefilters to completed tasks only, whenFalsefilters to in-progress tasks only; omitted from request when not provided. Also exposed ona8_find_harvest_taskMCP tool
Changed¶
- MCP
a8_upload_file,a8_find_files,a8_list_files— docstrings clarify thatbucket_idis provisioned by Authentic8 Support; no list API exists
[0.8.6] - 2026-03-26¶
Added¶
silo_sdk/utils/validators.py— shared parameter validation helpers:validate_nonempty_string,validate_optional_string,validate_positive_int,validate_nonnegative_int,validate_token_format. Exported fromsilo_sdk.utils.silo_sdk/base/types.py— 17 response TypedDicts exported from the public API:UserRecord,OrgRecord,FileRecord,TaskRecord,LogEntry,ContextRecord,EgressInfo,SSOConfig,ProxyRecord,PhoneEntry, and more.silo_sdk/utils/formatting.py—format_table()andto_csv()shared by scripts.silo_sdk/utils/org.py—walk_org_tree()(BFS generator) andprint_org_tree().BaseAPIClient._build_payload()— convenience method for constructing single-command API payloads; eliminates repeated[{"command": cmd, **params}]boilerplate.- 10 new MCP tools (49 total, up from 39):
a8_update_context,a8_list_egress_locations,a8_create_research_session,a8_wait_for_harvest_task,a8_download_harvest_result,a8_get_valid_task_types,a8_get_valid_task_params,a8_list_files,a8_get_log_types,a8_get_extract_logs_info - 4 new operational scripts:
scripts/audit_org_users.py— compliance audit: inactive, suspended, missing-field usersscripts/batch_user_provisioning.py— bulk user creation from CSV with dry-runscripts/analyze_log_storage.py— log type availability and sequence infoscripts/compare_org_configs.py— proxy policy / user count drift across org hierarchyexamples/batch_operations_demo.py— bulk context creation with per-item error capture, retry, and cleanup summary.- MCP tests:
tests/test_mcp_credentials.pyandtests/test_mcp_tools.py(skipped automatically whenmcppackage is not installed). tests/test_utils_formatting.pyandtests/test_utils_org.py.
Changed¶
- All 39 MCP tools now translate SDK exceptions to MCP-friendly messages:
ValidationError/ConfigurationError→ValueError;SiloError→RuntimeError. scripts/move_users.py— per-user progress feedback during execution.examples/harvest_demo_async.py— added timeout handling (asyncio.wait_for), partial-failure handling, and progress note.scripts/move_users.pyandscripts/session_report.py— table output now uses sharedformat_table()fromsilo_sdk.utils.formatting.- CI matrix — dropped Python 3.9 (EOL October 2025); minimum is now Python 3.10.
Updated
requires-python = ">=3.10". blackupgraded>=26.3.1. Pre-commit rev synced.
Fixed¶
tests/test_api_tag_on_static_method—callable(staticmethod)guard for Python 3.9 (now skip-pathed viasys.version_info; no longer breaks on 3.10+ either).tests/test_import_error— changedmock.patch(string)tomock.patch.object(module)for reliable behaviour across Python 3.10–3.13.tests/test_mcp_tools.py— addedpytest.importorskip("mcp")guard so CI does not fail when themcppackage (in[mcp-server]extra) is not installed.- Postman collection flow assertions — corrected Log Pipeline and Context Lifecycle flows:
get_log_info(non-existent command) →extractlogprobe;resultarray →result.logsarray;AUTH,SESSIONmulti-type → singleAUTHfor this token. docs/getting-started/authentication.mdanddocs/concepts/tokens.md— removedget_log_info(confirmed via live API testing that this command does not exist).docs/maintenance/api-recommendations.md— updated Log Sequence Metadata section to reflect SDK workaround viaextractlogprobe (v0.8.5+).- Dependency audit — a known vulnerability in pygments is deferred pending an upstream fix.
[0.8.5] - 2026-03-25¶
Added¶
BrowsingAPI.update_context()— new method to modify an existing browsing context. Acceptsbrowse_context_idplus any combination ofcontext_data,max_uses,expires,name, andenabled. Only supplied fields are updated; omitted fields are unchanged. Registered incommand_registry.py.extract_logs()include_suborgsparameter — optionalboolthat, whenTrue, includes log records from child organizations in the extraction result. Omitted from the request when not provided (no change to existing behavior).singleharvest task type — added toVALID_TASK_TYPES. Single-URL non-recursive fetch; useswget_params; available from all egress locations (unlikeasset).VALID_ASSET_EGRESS_LOCATIONS— new constant inegress_validation.pylisting the six datacenter locations valid forassettasks: Singapore, Dubai, Frankfurt, Sao Paulo, Johannesburg, New York City / New York, NY.- Three missing egress locations added to
VALID_EGRESS_LOCATIONSfor video/visual/single tasks: Kuwait City, La Paz, Monterrey (confirmed from official API documentation Appendix A). - 39 new unit tests across
test_browsing_api.py,test_egress_validation.py,test_harvester_api.py,test_log_extraction_api.py, andtest_user_management_api.py. Total: 1,124 tests, 99.28% coverage. - Postman collection v0.8.5 —
session_reportrequest corrected, newUpdate Contextrequest added to Browsing Isolation section,include_suborgsshown in Extract Logs example.
Changed¶
get_user()signature —usernameandemailare now keyword-only and mutually exclusive. Live API testing confirms the API returns an error if both are supplied simultaneously. Pass exactly one:api.get_user(username="u@x.com")orapi.get_user(email="u@x.com").get_session_report()signature —include_childrenparameter removed. Confirmed via live API testing this was never a real API parameter and was silently ignored by the server. The method now acceptsorg_name,start_date,end_date, andusername.- Asset task egress validation —
create_harvest_task(task_type="asset", ...)now raisesValidationErrorif the suppliedegress_info["name"]is not one of the six valid asset egress locations (source: Harvester API Documentation Appendix A, Feb 2026). Previously any location from the full list was accepted and silently sent to the API. VALID_EGRESS_LOCATIONSreorganised — list sorted consistently within regions; three previously missing locations added.
Fixed¶
scripts/session_report.py— removed--no-childrenCLI flag andinclude_childrenkwarg that was silently ignored by the API.scripts/move_users.pyandexamples/user_demo.py—get_user()calls updated from positional argument to keyword argument (username=) to match the new signature.silo_mcp/tools/orgs.pya8_get_session_report— removedinclude_childrenfrom the MCP tool signature.
Breaking changes¶
get_user(username)positional call → useget_user(username=username)orget_user(email=email). Passing both raisesValidationError.get_session_report(include_children=...)→ remove the argument; it no longer exists.create_harvest_task(task_type="asset", egress_info={"name": "Tokyo"})→ raisesValidationError. Asset tasks must use one of the six valid datacenter locations.
[0.8.4] - 2026-03-16¶
Added¶
update_partner_sso_config— newOrgManagementAPImethod to update an existing Partner SSO configuration. All IdP fields (idp_name,idp_login_url,idp_cert,vanity_url,new_org_name,parent_org_name) are optional; only supplied fields are sent. Also works to add SSO to an existing org that has no SSO config yet.delete_partner_sso_config— newOrgManagementAPImethod to permanently delete a Partner SSO configuration, its partner user, and the org itself. Irreversible.- Both new methods registered in
command_registry.pyand exposed as MCP tools (a8_update_partner_sso_config,a8_delete_partner_sso_config) — MCP server now has 37 tools. manage_sso.py— new standalone script providing a full CLI for all 6 Partner SSO API operations (create,get,update,delete,enable,disable) plus animport-metadatasubcommand that parses live SAML IdP metadata XML (from file or URL) to auto-populate SSO config.silo_sdk/utils/saml.py— new SAML IdP metadata parsing utilities (fetch_metadata_url,parse_saml_metadata) used bymanage_sso.pyand thea8_parse_saml_metadataMCP tool.a8_parse_saml_metadataMCP tool — parses a SAML IdP metadata XML document and returns structured IdP config (entity ID, login URL, certificates).update_collection_examples.py— repeatable script that runs Newman against the Postman collection, captures real sanitized API responses, and writes examples back to the collection JSON. Supports multi-pass error capture, SSO org state persistence, and SAML metadata fetching.docs/maintenance/api-recommendations.md— new page documenting confirmed API gaps (org.update_settingsmissing,list_harvest_tasksmissing,get_log_infomissing) and resolved gaps (proxy policy write ops confirmed aspolicy_proxies.set/add/delete).- 20 new unit tests for SSO methods (1055 total).
Changed¶
create_partner_sso_config/get_partner_sso_configdocstrings — documented the full real API response schema (SP_cert,SP_entity_id,a8_portal_url,a8_postback_url,partner_SSO_sign_cert) sourced from official API documentation and live reference code.get_partner_sso_configdocstring adds behavioral note: the API returns an error (not empty dict) when SSO is disabled for the org.create_org()docstring corrected — always creates a sub-org (not top-level); documents parent defaulting to token scope when omitted, SCIM group provisioning use case, andvanity_urlas optional here vs required for SSO.get_log_sequence_info()reimplemented — theget_log_infocommand is not recognized by the ext API. Now implemented as aCONVENIENCEmethod that probes viaextractlog(start_seq=0, limit=1, type=AUTH). Returnsmin_seq,is_more,next_seq;max_seqandtotal_logsare explicitlyNone(not available via ext API).extract_logs()bug fixed — was failing to unwrap the{"result": {...}}response envelope, causinglogsto always be empty andis_more/next_seqto beNone. The fix adds a single unwrap step consistent with how all other API modules handle responses.export_logs.pyfixed —show_info()updated to displayearliest seq/is_more/next_seqinstead of the unavailablemin_seq/max_seq/range. Defaultstart_seqlogic now usesor 0to gracefully fall back when no logs are found at the probe point.a8_create_orgMCP tool description updated to match correctedcreate_org()docstring.a8_extract_logsMCP tool — corrected Returns (removed fabricatedtotal_count; documents actuallogs/is_more/next_seqfields).a8_get_log_sequence_infoMCP tool — updated to reflect probe-based implementation;max_seq/total_logsdocumented asNone.create_harvest_taskdocstring — corrected wire key names (wget_params/vid_paramsnotasset_params/video_params); full wire format example now shows correcttask_paramsnesting.download_filedocstring — added Note documentingPOST /getfile/endpoint with form-dataid+auth(not a command-array request).create_contextdocstring — added Note about GET/ctx/shorthand withresponse=parameter; updatedcreate_ctx_urlto cross-reference.create_partner_sso_configdocstring —parent_org_nameclarified as required in practice.docs/concepts/wire-protocol.md— new Non-Command-Array Endpoints section documentingPOST /getfile/,POST /putfile/, andGET /ctx/withresponse=parameter table.- MCP
a8_create_harvest_task— warns against invalidtask_typevalues (screenshot,pdf,mhtml);a8_create_partner_sso_configclarifiesparent_org_namerequirement. - Postman collection updated: 60 standalone requests now have real sanitized API response
examples; harvest task wire format corrected;
Download Filecorrected toPOST /getfile/;Upload Fileform field names corrected (name/path);GET /ctx Shorthandupdated withresponse=urlparameter. scripts/README.md—update_collection_examples.pydocumented with usage examples, SSO org reset procedure, and required env vars.- Version bumped to
0.8.4acrosspyproject.toml,silo_sdk/__version__.py, User-Agent strings,silo_mcp/__init__.py, config files, and docs.
Removed (fabricated commands that do not exist in the ext API)¶
update_org_settings()removed fromOrgManagementAPI— theorg.update_settingscommand is not recognized by the ext API. Org settings (session limits, MFA, download policies) are not configurable via the API.a8_update_org_settingsMCP tool removed.list_harvest_tasks()removed fromHarvesterAPI— thelist_harvest_taskscommand is not recognized by the ext API. Usefind_harvest_task(task_id)for point lookup, or extractHARVESTlog type entries to find task IDs.a8_list_harvest_tasksMCP tool removed.settingsparam removed fromcreate_org()— was unverified and unused.
Added (proxy policy management — confirmed real API commands)¶
get_proxy_policy()— newOrgManagementAPImethod usingpolicy_proxies.getset_proxy_policy()— replaces entire proxy list (policy_proxies.set)add_proxy_policy()— appends proxy objects; multiple in one call (policy_proxies.add)delete_proxy_policy()— removes by name; multiple names in one call (policy_proxies.delete); always returns HTTP 200 withproxies_deletedandproxies_not_foundarrays- All four methods exposed as MCP tools:
a8_get/set/add/delete_proxy_policy— MCP server now has 39 tools - Proxy object schema documented:
name(required, unique) +location.address(required);portandtypeoptional (http/https/socks/socks4/socks5); HTTPS cert CN must match address docs/concepts/wire-protocol.md— Proxy Policy Commands section with schema, type table, delete response format, and hierarchy aggregation note
Added (browsing policies)¶
docs/concepts/browsing-policies.md— new reference page documenting all confirmed browsing context policy types:readonly,file_transfer,clipboard,browser_chrome(standard/seamless/minimal),domain_allow,domain_block,ribbon_*- Seamless UI constraint documented:
browser_chrome: seamlessonly available for non-catchall users; catchall users are always forced to minimal (Ribbon Mode) egress_regionvalidation added toBrowsingAPI.create_context()— validates againstVALID_EGRESS_LOCATIONS(shared Silo Workspace egress network). Documents mixed-licensing caveat: per-user entitlements are enforced at launch, not at SDK level.
Added (test coverage and quality)¶
tests/test_saml.py— 29 new tests forsilo_sdk/utils/saml.py(0% → 100% coverage)tests/test_coverage_gaps.py— 24 new tests covering remaining gaps in org_api, extraction_api, file_api, tags, harvester_utils, harvester_api, log_schemas, key_management- Overall coverage: 96.4% → 99.3%; total tests: 1,105 passing
Added (Postman collection and update tooling)¶
- Postman collection v2 merged from manual review: org requests in "Org Structure" subfolder,
corrected proxy policy commands, new
GET /ctx with X-Auth Header,Create Organization,Add/Delete Proxy Object,Get API Session Reportrequests;List Tasks/Update Org Settingsremoved (commands do not exist) ctx_urlenv var added to all four Postman environment files — enables multi-environment GET /ctx testing without hardcoded production URLsproxy_nameenv var added to all four Postman environment files- Example naming convention updated:
"Valid Response"/"Error: <summary>"replaces"200 OK – Real Response"/"400 BAD REQUEST – Error Response" - API-level error detection in update script: checks
response[1]["error"]and nestedresult.error— captures errors that HTTP 200 responses previously hid - Eng environment support in
update_collection_examples.py:A8_CONFIG_ENV=enguses.env.engoverrides; env-specific state files (.collection_update_state.eng.json); Newman env URLs derived fromA8_API_URL; 57/60 standalone requests updated against eng dest_pathchanged from/harvester_results/to/postman/in all environment files and the Newman env builder
Changed (API correctness and documentation)¶
create_org()docstring corrected: always creates sub-org;vanity_urloptional here but required for SSO; org hierarchy root documented; SCIM context addedbrowser_chromepolicy type added toBrowsingAPIclass docstring (3 values:standard,seamless,minimal); Seamless UI auth constraint documentedadd_proxy_policydocstring — clarifies multiple proxies can be added in a single calldelete_proxy_policydocstring — clarifies multiple names can be deleted in a single call- Phone area code validation documented: 555 area codes are rejected by the user management API (not enforced by Admin Console); all SDK examples and tests updated to use 888 area code
- Egress region names corrected across
egress_validation.pyandisolation_api.py:Asia-Pacific,Africa & Middle East,Central & South America docs/maintenance/api-recommendations.mdexpanded with: proxy policy resolution,list_harvest_tasksconfirmed missing with HARVEST log workaround, per-user egress availability gap, role-based permission assignment gap with Claims-based provisioning customer request
[0.8.3] - 2026-03-04¶
Added¶
- Postman API Reference Collection — fully audited and corrected; 112 requests across 7 numbered folders, 100% Newman pass rate against Production
- All request bodies corrected to use flat wire format (params alongside
"command", not nested under"data") create_contextfixed:"user"/"urls"(array) instead of"username"/"url"(scalar)- Harvest task types corrected:
"visual"/"asset"/"video"only;"screenshot"removed - SSO configuration chain fixed: uses
org_name(notsso_config_id, which doesn't exist) - Auto-generated
test_org_nameandtest_sso_org_namein pre-request scripts - Proxy policy and
org.update_settingstests made graceful for permission-limited accounts GET /ctxshorthand URL corrected (removed erroneous/api/prefix)Find Files — Date Rangerequest added demonstratingfile_type,:created_before,:created_afterfilter params- Newman testing guide (
docs/postman/newman-testing.md) — setup, commands, environment variable mapping, CI/CD GitHub Actions workflow, known limitations - Wire format
Note:sections added to five SDK methods documenting Python→wire field name and type differences: BrowsingAPI.create_context:username→"user",url→"urls"(array),max_usesplacementHarvesterAPI.create_harvest_task: validtask_typevalues,task_params→vis_paramsmappingLogExtractionAPI.extract_logs:log_typeslist→comma-joined string, date→epoch transformsOrgManagementAPI.create_partner_sso_config:idp_name→"IdP_name"(case-sensitive)FileAPI.find_files:file_type→"type",created_before→":created_before"(colon prefix)Attributes:sections added toBrowsingAPI,HarvesterAPI, andLogExtractionAPIclass docstrings so mkdocstrings rendersVALID_TASK_TYPES,VALID_VISUAL_PARAMS,VALID_LOG_TYPESas structured tables in the MkDocs API reference- MkDocs nav: new "Postman & Newman" section linking to the Newman testing guide
Fixed¶
.mcp.jsonpointed to outdated repository path; corrected.- MCP server
mcp-serverextras not installed;pip install -e ".[mcp-server]"now required for the MCP server to start (installsmcp,fastapi,uvicorn) - Wire format example in developer docs showed incorrect
"data": {…}nesting; corrected to flat params - Wire format example in
docs/concepts/wire-protocol.mdshowed wronglistusersstructure docs/getting-started/authentication.md:API_URLexample had erroneous/api/suffixdocs/getting-started/quickstart.md: misleadingtask_type="screenshot"→task_type="visual"docs/concepts/async.md:task_type="screenshot"→task_type="visual"docs/getting-started/quickstart.mdand 13 other files: stalev0.8.1/v0.8.2version references updated tov0.8.3- User-Agent string defaults updated from
silo-sdk/0.8.1→silo-sdk/0.8.3inapi_client.py,config.py,config_builder.py,config/default.json,config/production.json silo_mcp/__init__.pyversion bumped to0.8.3
Changed¶
- Postman environment defaults updated:
org_namemaps toA8_TOP_ORG(notA8_ORG_VANITY_URL) - Postman collection
info.versionset to0.8.3 - Developer tooling documentation expanded: flat-param rule,
create_contextexception,orgvsorg_nameparam distinction,A8_TOP_ORGvsA8_ORG_VANITY_URLdistinction
[0.8.2] - 2026-03-03¶
Changed¶
- Simplified README for public PyPI project description
- Removed internal email references from all public-facing files
- Fixed Documentation URL to point to GitHub Pages
- Updated Dockerfile maintainer labels
[0.8.1] - 2026-03-03¶
Package rename: outdated repository path → silo-sdk.
Changed¶
- Package renamed from its outdated repository path to
silo-sdk - Import path changed from
a8_apistosilo_sdk - Exception base class renamed from
A8APIErrortoSiloError A8APIErrorkept as backward-compatible alias- Repository renamed to
silo-sdk-python - User-Agent string updated to
silo-sdk/0.8.1
Migration¶
# Old
from a8_apis import BrowsingAPI, A8APIError
# New
from silo_sdk import BrowsingAPI, SiloError
# A8APIError still works as alias
from silo_sdk import A8APIError # backward compat
[0.8.0] - 2026-03-03¶
Project reorganization and documentation overhaul.
Added¶
- 6 API quick reference guides (browsing, user management, org management, file storage, log extraction, decryption)
- Zensical migration readiness assessment (
docs/maintenance/zensical-migration.md) - Archived tools documentation page (
docs/development/archived-tools.md)
Changed¶
- Version rolled back from 1.0.0 to 0.8.0 to reflect pre-production beta status
- pyproject.toml classifier changed to
Development Status :: 4 - Beta .gitlab-ci.ymlmoved to.gitlab/ci.ymlfor better organization- Aligned ignore file patterns across
.gitignore,.dockerignore,.claudeignore - Archived
tools/postman_validator/(restored via git history if needed) - Archived
Documentation/Authentic8_Unix_Log_Extract_Package_1.0.3/toDocumentation/archive/ - Removed
tools/exclusions from.pre-commit-config.yaml - Improved 14 documentation pages for user-friendliness and accuracy
- Documentation site expanded to 38 pages
Fixed¶
- Broken links in
docs/development/archived-tools.md - Outdated version references across documentation
[0.7.0] - 2026-03-01¶
MkDocs documentation portal, MCP server, and CI/CD automation.
Added¶
- MkDocs developer portal (28 pages) with Material theme and mkdocstrings
- Getting started guides (installation, quickstart, configuration, authentication)
- How-to guides (bulk harvest, decrypt logs, export logs, manage users)
- Concepts documentation (async, errors, tokens, wire protocol)
- Auto-generated API reference for all 7 modules
- Maintenance playbooks (branch protection, CI runbook, release checklist)
- MCP server with 34 tools exposed via Model Context Protocol
- Dual transport: stdio + SSE (Server-Sent Events)
- FastAPI-based server implementation
- Reliable environment loading wrapper script
- CI/CD automation:
- GitHub Actions workflows for quality checks, security audits, docs deployment
- Weekly dependency audit workflow
- GitLab CI/CD configuration with Component Catalog structure
templates/sdk-test.ymlreusable CI component- GitHub Pages deployment via
mkdocs gh-deploy - PyPI packaging with OIDC Trusted Publisher (later disabled for release-only distribution)
[0.6.0] - 2026-02-28¶
Native log decryption and log type schemas.
Added¶
- Native log decryption (
a8_apis/logging/decrypt.py): - Standard decryption: EC + HKDF-SHA384 + AES-256-GCM
- Legacy decryption: seccure/secp256r1
standard_decrypt()— in-memory decryptionstandard_decrypt_chunked()— 32 MB streaming for large filesdecrypt_video_file()— atomic write with temp fileload_private_keys(),decrypt_log_entry(),decrypt_logs()- Private key file format:
name=valuepairs inpvtkey.txt - Log type CSV schemas (
a8_apis/logging/log_schemas.py): - Canonical field ordering for 24 log types
- 25 total log types including
APP_LAUNCH,EVENT,EXTENSION - Schemas synced with Authentic8 package v1.0.3
- Optional package extras:
[decrypt]and[legacy-decrypt] run_afterparameter for delayed harvest task execution (Unix epoch or ISO 8601)- All 30+ vis_params and video_params synced with Harvester API v2.14
- Authentic8 Log Extract Package v1.0.3 added as reference
- Python 3.9-3.13 CI test matrix (expanded from 2 to 5 versions)
[0.5.0] - 2026-02-20¶
Standalone scripts, SDK examples, and packaging.
Added¶
- 8 standalone scripts in
scripts/: export_logs.py— log extraction with pagination, resume, decryption, proxy supportkey_manager.py— CLI for managing private keys and API tokensbulk_create_contexts.py— bulk browsing context creation from URL listsession_report.py— organization session report generatorfile_manager.py— file storage CLI (list, search, download, info)scheduled_visual_harvest.py— daily visual captures withrun_afterschedulingdecrypt_files.py— batch log file decryptionmove_users.py— user migration between organizations- SDK demos in
examples/: user_demo.py,org_demo.py,log_demo.py,decrypt_demo.pybrowsing_demo.py,file_demo.py,config_demo.pyharvest_demo_sync.py,harvest_demo_async.py- CONTRIBUTING.md with development workflow
.env.exampletemplate with allA8_*environment variables- Unified Postman API Reference Collection (100% SDK coverage)
Changed¶
examples/renamed tosdk_examples/for clarity (reverted toexamples/in cleanup)- Repository URLs updated to reflect current hosting
- Removed legacy Postman collections (replaced by API Reference Collection)
[0.4.0] - 2026-02-15¶
Security hardening and near-complete test coverage.
Security¶
- 6 CVEs fixed:
- urllib3 CVE-2026-21441 (CVSS 8.9): DoS via decompression bomb on redirect responses; fixed in urllib3 2.6.3
- certifi CVE-2024-39689 (CVSS 7.5): Untrusted GLOBALTRUST root certificate (MITM risk); fixed in certifi 2024.07.04
- aiohttp CVE-2024-52304: Request smuggling via chunk extension parsing
- aiohttp CVE-2025-53643: Request smuggling via chunked trailer parsing; fixed in aiohttp 3.12.14
- aiohttp CVE-2025-69223: Zip bomb DoS via unbounded decompression; fixed in aiohttp 3.13.3
- cryptography CVE-2026-26007 (CVSS 8.2): Subgroup attack on SECT binary curves leaks private key bits via ECDH; fixed in cryptography 46.0.5
- All dependencies pinned with upper bound constraints
- Replaced
randomwithsecretsfor cryptographic operations - Fixed command injection vulnerability in
setup_venv.py
Added¶
- 76 additional tests pushing coverage from 94% to ~99%
Changed¶
- aiohttp and certifi made mandatory dependencies (no longer optional)
- All security audit findings resolved
- 3 flake8 unused import warnings fixed
[0.3.0] - 2026-02-10¶
Test coverage, quality gates, and SDK method tagging.
Added¶
- SDK method tagging system:
@api_tagdecorator for method classification (API_COMMAND, CONVENIENCE, ASYNC_VARIANT, UTILITY)COMMAND_REGISTRY— single source of truth for SDK method to wire command mapping- Unified Postman collection with SDK generator and validation tools
- Pre-commit hooks: black, isort, flake8, mypy, bandit, pylint
- pytest-asyncio for async test support
- Custom developer tooling slash commands for development workflows
Changed¶
- Test coverage pushed from ~60% to 94.22% (500+ tests)
- Fixed 19 async test failures caused by mock setup bugs
- MyPy type annotations added across the entire codebase (61 errors to 0)
types-requestsadded to dev dependencies for mypy stub resolution
[0.2.0] - 2026-02-05¶
All 6 API modules functional with async support.
Added¶
- Silo for Research session type in BrowsingAPI
- Full async support in HarvesterAPI and FileAPI
- Claude Code agents for development workflow
Changed¶
- Async utility functions consolidated into HarvesterAPI class (removed 600+ lines of duplicate code)
- FileAPI refactored for improved reliability
- HarvesterAPI end-to-end workflow verified
APIs Completed¶
- Browsing Isolation API (
a8_apis.browsing.BrowsingAPI) - User Management API (
a8_apis.management.UserManagementAPI) - Organization Management API (
a8_apis.management.OrgManagementAPI) - File Storage API (
a8_apis.storage.FileAPI) - Log Extraction API (
a8_apis.logging.LogExtractionAPI) - Web Harvesting API (
a8_apis.harvesting.HarvesterAPI)
[0.1.0] - 2026-01-30¶
Initial SDK framework.
Added¶
- Base API Client (
a8_apis.base.BaseAPIClient): - HTTP session management with retry logic
- Authentication handling via Bearer tokens
- Request/response logging
- Rate limiting support
- Comprehensive error handling
- Configuration system (
a8_apis.utils.config): - JSON configuration file loading
- Environment variable substitution (
${VAR}and${VAR:-default}) - Validation via
validate_config_for_api() - Exception hierarchy (
a8_apis.base.exceptions): - Base
A8APIErrorwith HTTP status and request ID tracking - Module-specific exceptions (BrowsingAPIError, FileAPIError, etc.)
- Initial implementations of BrowsingAPI, FileAPI, HarvesterAPI
- Setup script with virtual environment creation
- Project structure:
a8_apis/package withbase/,browsing/,storage/,harvesting/,logging/,management/,utils/submodules
Package Metadata¶
- License: MIT
- Python: 3.9+ (initial release; minimum raised to 3.10 in v0.9.1)
- Dependencies: requests, urllib3, aiohttp, certifi, python-dotenv