Skip to content

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-mcp distribution on PyPI — pip install silo-sdk-mcp now works. It contains no code. It is an alias that depends on silo-sdk[mcp-server] at an exact matching version, so it installs precisely what pip install "silo-sdk[mcp-server]" installs and nothing else. The MCP server continues to ship inside silo-sdk as the silo_sdk_mcp package and the silo-sdk-mcp console 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 for silo-sdk-mcp only 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 by silo-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 default pip install resolved 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 the decrypt and legacy-decrypt extras — 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.1 in the mcp-server extra (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-server extra could not install at its own declared minimums, and its lowest supported version crashed the HTTP transport. mcp[cli]>=1.0.0 admitted releases with no mcp.server.transport_security module — added in 1.10.0 — which the server imports unconditionally to configure DNS-rebinding protection. Any such release installed cleanly and then raised ModuleNotFoundError the moment the server was started over HTTP or SSE. Separately, the extra declared fastapi>=0.111.0 alongside mcp, and their transitive Starlette requirements were disjoint (<0.38 against >=0.39), so installing the declared minimums failed outright with a resolution error. Both are corrected; uvicorn[standard] moves to >=0.31.1 because that is what the new mcp floor requires.
  • pyyaml >=6.0.0>=6.0.2 in the mcp-server extra. 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

  • fastapi is no longer a dependency of the mcp-server extra. It was never imported. The MCP server is built on FastMCP, 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 imported fastapi and 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-mcpsilo-sdk-mcp rename below is a breaking change to a published interface.

Added

  • New LOG_EXTRACT_TIMEOUT config key (A8_LOG_EXTRACT_TIMEOUT) — a dedicated request timeout for LogExtractionAPI, separate from the SDK-wide REQUEST_TIMEOUT. It ships unset, and LogExtractionAPI falls 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_ROOT environment variable for the MCP server — the directory (or PATH-style list of directories) that a8_upload_file and a8_download_file are allowed to read and write. Defaults to a dedicated silo_sdk_mcp_files directory 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, and RETRY_DELAY are now environment-templated in config/default.json, so A8_REQUEST_TIMEOUT / A8_MAX_RETRIES / A8_RETRY_DELAY take 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. Unlike bulk_create_contexts.py, the destination URL is optional: omitting --url opens the session's default start page, which is the usual way to start an ad-hoc Silo for Research session. One of --user or --org is 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-python is 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.1 tag remains the last release that installs on 3.10. Installing this version or later on 3.10 fails cleanly at resolution time with a Requires-Python: >=3.11 error rather than installing something broken, so pin to v0.13.1 or 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.
  • LogExtractionAPI now 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 with PermissionDenied: log.extract already in progress until 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-second REQUEST_TIMEOUT still governs the sub-second admin and browsing calls that dominate the SDK. Set A8_LOG_EXTRACT_TIMEOUT to override.
  • The new default raises the extraction timeout but never lowers it. Anyone who had already set REQUEST_TIMEOUT above 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 explicit LOG_EXTRACT_TIMEOUT still wins outright, including one below REQUEST_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", not 30 — so making the timeout and retry keys templatable would otherwise have handed a string to requests and broken validate_harvester_config()'s isinstance(value, (int, float)) check with a misleading "must be a positive number". REQUEST_TIMEOUT, LOG_EXTRACT_TIMEOUT, MAX_RETRIES, RETRY_DELAY, and STATUS_CACHE_TTL are coerced after substitution and before defaults are applied, on both config-loading paths. A non-numeric value now raises ConfigurationError naming the key instead of failing later somewhere unrelated. DEFAULT_MAX_USES is 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, and STATUS_CACHE_TTL are seconds, and requests is happy with 2.5; MAX_RETRIES is 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 while A8_RETRY_DELAY=2.5 did 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_TIMEOUT fails every request before it is sent, so it is now a ConfigurationError; 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 as 1.
  • CLI entry point and Python package renamed: silo-mcpsilo-sdk-mcp, silo_mcpsilo_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 the silo-sdk distribution the server ships in. The package path was the last name that did not match, and silo-mcp was ambiguous enough to be worth retiring outright.
  • Action required: update the server key and command in 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 user then claude mcp add silo-sdk-mcp --scope user -- <command>.
  • Action required (importers and python -m users): python -m silo_mcp becomes python -m silo_sdk_mcp, and any from silo_mcp… import … becomes from 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). The silo-sdk-mcp console script and, for editable installs, the generated import finder both bake the module path in at install time and do not update on a git pull — until you reinstall, the console script fails with ModuleNotFoundError: No module named 'silo_mcp'. Note that python -m silo_sdk_mcp run from the repository root keeps working regardless, because the current directory is on sys.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.yamlsilo_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 orphaned silo_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_mcp to silo_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] in pyproject.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 S rules are flake8-bandit — the same rule set bandit ran — and D runs under the pep257 convention flake8-docstrings defaulted to, with the same rules disabled as before. E/W/F/B/C4 cover 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 and pip-audit for dependency CVEs. Pylint is simply gone — it ran with --exit-zero, so it never gated anything.
  • One formatting pass touched 26 files. ruff format is 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/ and setup_venv.py were 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/604Dict[str, Any] is now dict[str, Any], Optional[X] is X | None, and the typing imports they needed are gone. Ruff's UP rules 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 ships py.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]" and pre-commit install --install-hooks. black, isort, flake8, bandit and pylint are no longer dev dependencies, and .flake8 has been deleted.
  • OrgManagementAPI.delete_org() now raises instead of returning False. The method advertised True on success and False on failure, but False was unreachable: the server answers a delete with either a confirmation payload or an error, and an error already raised OrgManagementAPIError. The False branch could only be reached by a response shape the API does not produce, so in practice the method already either returned True or raised — while its signature invited callers to write if 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 an OrgManagementAPIError naming 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 OrgManagementAPIError instead of testing the result. Code that only checks truthiness keeps working unchanged, because the success return is still True. Code that treated False as "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_org MCP tool description carried the same two errors and has been corrected to match.
  • dev now carries a .dev0 version suffix while it is ahead of the last tag. Until now dev reported the same __version__ as the release it branched from, so a pip install -e from a dev checkout was indistinguishable from the tagged release despite differing behavior — which made "which SDK produced this result?" unanswerable during a support investigation. dev will be set to <next>.dev0 immediately 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_file took any local path and sent its contents to the storage API; a8_download_file took 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 into a8_upload_file("/proc/self/environ") exfiltrates every A8_* token the server process holds, and a download aimed at ~/.ssh/authorized_keys or 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_files directory under the system temporary directory, created on demand. It is deliberately not the working directory and not the whole temp directory. Set MCP_FILE_ROOT to point somewhere real; it accepts a PATH-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_ROOT to 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_HOSTS previously 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.0 and an unauthenticated local attacker, and warnings scroll past. The server now exits with a non-zero status and an explanation. An explicitly empty MCP_ALLOWED_HOSTS is treated the same way, rather than as "allow everything".
  • --credentials pointing 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_response matched 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 containing token, password, secret, api_key, credential, private_key, session_id or authorization, 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_URL must use HTTPS. Nothing checked the scheme, so an http:// value — from a typo, a copied internal note, or an attacker-writable config file — silently sent every API token in cleartext. A non-HTTPS API_URL is now a ConfigurationError at 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_SSL config key. It appeared in config/default.json, config/production.json and the configuration reference, but no SDK code read it — certificate verification is unconditional on both the synchronous and the aiohttp paths, against the certifi trust store. A documented switch that does nothing is worse than no switch: it invites someone to set it to false, 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.sh writes 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 .env at mode 0600, via an atomic replace so an interrupted run cannot truncate a file full of credentials. Existing export A8_* lines are recognized and replaced rather than duplicated. Tokens already sitting in a ~/.zprofile from an earlier run should be removed by hand and rotated.
  • scripts/run_postman_eval.sh no longer passes tokens on the command line. Every account on the host can read the full argv of a running process out of ps, and CI runners log the command they invoked. The five tokens now travel in a short-lived 0600 environment 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.example carried 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.
  • .dockerignore excludes the per-environment config files. config/qa.json, config/eng.json and config/demo.json are gitignored, so unlike default.json and production.json nobody 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) and S323 (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.
  • seccure is now upper-bounded (>=0.5.0,<1.0) in the optional legacy-decrypt extra, matching every other dependency. It has had one release since 2014, so a surprise 1.0 would 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.mcp copies the whole working tree, and .dockerignore carried no entry for silo_sdk_mcp/credentials.yaml — so on any machine where the server had been run locally, docker build copied live API tokens into an image layer. .gitignore does 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 to MANIFEST.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_TIMEOUT but never lowers it — was keyed on LOG_EXTRACT_TIMEOUT being absent from the configuration. But config/default.json shipped "${A8_LOG_EXTRACT_TIMEOUT:-600}", which resolves to 600 whether or not the environment variable is set, so the key was always present and always looked deliberate. Anyone who had raised REQUEST_TIMEOUT above 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 explicit 600 is 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 with LOG_EXTRACT_TIMEOUT must be a number, got ''. Because every CLI script under scripts/ loads the configuration at import time, that turned --help into a traceback on a machine with no A8_* 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() and print_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 # nosec marker, which has been inert since bandit was replaced by Ruff's S rules — Ruff reads noqa.)
  • scripts/run_postman_eval.sh ignored A8_CONFIG_ENV and always ran against production. The variable was documented in the script's own usage text, but the Production environment file was hardcoded — so A8_CONFIG_ENV=eng ./scripts/run_postman_eval.sh ran 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 now eng rather than prod: 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. E501 was listed in Ruff's ignore, and ignore takes precedence over select — so the max-line-length = 100 setting alongside it, and the changelog note in this release saying the lint gate still fails at 100, described a check that could not fire. E501 has 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.example or silo_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 on MANIFEST.in to reach the wheel.
  • Dockerfile.mcp installed 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 only pyproject.toml produced an import finder with an empty mapping, and the later source copy could not retroactively fix it. Inside the image the silo-sdk-mcp console script failed with ModuleNotFoundError and imports only worked from /app. This was masked by the container's own CMD, which uses python -m and therefore picks the package up from the working directory regardless. The source is now copied before the install.
  • Numeric values in credentials.yaml were 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 a credentials.yaml written in the documented "${A8_REQUEST_TIMEOUT:-30}" style handed requests the string "30" and every call from an HTTP-transport client failed with a TypeError from inside the HTTP layer. Both loading paths now share one coercion step, so they cannot drift apart again.
  • silo-sdk-mcp --credentials defaulted to a path relative to the current directory. The default was silo_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 /app in the image).
  • The Compose file's MCP_ALLOWED_HOSTS default ignored MCP_BIND_HOST. Binding the container to a non-loopback address left the DNS-rebinding allow-list pinned to 127.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 from MCP_BIND_HOST, and an explicit MCP_ALLOWED_HOSTS still overrides it.
  • The remote-deployment documentation described the superseded SSE transport as the default. The setup and overview pages showed --transport sse, the /sse endpoint in every reverse-proxy example, and an MCP_CREDENTIALS_FILE environment variable the server does not read; the published port example also bound 0.0.0.0. They now document streamable-http and its /mcp endpoint, bind the published port to loopback so a reverse proxy is the only route in, and note where MCP_ALLOWED_HOSTS must list the public hostname. sse still works and is still documented as the fallback for older clients.
  • The same correction reached the packaged silo_sdk_mcp/README.md and the CLI's own --help text, which still described --port and --credentials as SSE-only and showed a /sse client 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 --host defaults to 0.0.0.0 — every interface — and points at --host 127.0.0.1 behind a TLS-terminating proxy.
  • The configuration reference omitted seven shipped config keys. TOP_ORG appeared only as a script-scoped environment variable despite being required by a8_validate_config and probed by a8_health_check, and ENABLE_STATUS_CACHE, STATUS_CACHE_TTL, RATE_LIMIT, DEFAULT_EGRESS_INFO, DEFAULT_POLICY and ENABLE_REQUEST_LOGGING were absent entirely — all six ship in config/default.json, so readers met them for the first time in a file the docs never explained. The A8_REQUEST_TIMEOUT / A8_MAX_RETRIES / A8_RETRY_DELAY mappings were missing from the environment-variable table as well. All are now documented, along with a note that ORG_VANITY_URL is not the API org name — TOP_ORG is. Two are documented as inert rather than as working settings: nothing reads ENABLE_REQUEST_LOGGING, and RATE_LIMIT is shape-validated but never enforced, so the SDK does not throttle on your behalf.
  • The remote-deployment documentation showed a two-client credentials.yaml that 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__ and config.__version__. Both were sub-package version numbers with no consumers, and both had silently drifted from the distribution version (the MCP sub-package, then named silo_mcp, sat at 0.10.3 through four releases; config at 0.8.2 since April). silo_sdk.__version__ is the single source of truth — as DEFAULT_USER_AGENT already 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 defensive raise statements 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_retries to a8_extract_logs skips 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 by a8_parse_saml_metadata and the SSO-import CLI command):
  • fetch_metadata_url() now only accepts http/https URLs 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 clear ValueError before any fetch is attempted.
  • MCP downloads that omit output_path now 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 with defusedxml instead of the standard library parser, which rejects entity-expansion ("billion laughs") payloads and external entity references (XXE) up front instead of resolving them. defusedxml is now a required dependency.
  • parse_saml_metadata() no longer silently uses the first EntityDescriptor when 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 optional entity_id parameter 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-GCM InvalidTag, the exact signal that a ciphertext was tampered with or corrupted — and returned a dict with just an error field 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 an error key, and decrypt_logs() appended it straight into the "successfully decrypted" list. Decryption/authentication failures are no longer swallowed:
  • decrypt_log_entry() now raises the new LogDecryptionError on 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 new track_failed=True to instead collect failed entries into a separate list and keep processing the rest of the batch — mirroring the existing track_missing parameter 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 error key should instead catch LogDecryptionError (or pass track_failed=True and 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 inside finalize(), which runs after plaintext chunks have already been written via update() — 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 the pvtkey.txt store (necessary since a multi-line PEM value can't be represented on the store's single name=value line), but load_private_keys() never decoded it back — so the documented generate_key_pair()add_key_to_store()load_private_keys() → decrypt workflow hard-failed with a ValueError inside load_pem_private_key(). load_private_keys() now automatically detects and decodes base64-encoded PEM values written by add_key_to_store(); Legacy passphrases, .pem file paths, and hand-written raw PEM values are left untouched.
  • ENC log schema listed the wrong field name. The ENC CSV schema in log_schemas.py listed enc_algorithm, but the Authentic8 API documentation's Extract Log response example confirms the actual field returned alongside encrypted log entries is encryption_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 to Legacy regardless of its actual encryption type. Corrected to encryption_type.
  • HTTP retries could re-execute non-idempotent requests. The shared Retry adapter allowed POST on 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_methods is now restricted to idempotent verbs only (GET, HEAD, PUT, DELETE, OPTIONS).
  • batch_request returned the injected setauth acknowledgment 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_users also now applies the same "user not found" soft-success translation delete_user() already had, so the batch and single delete paths present a consistent result contract.
  • modify_file posted directly to the base URL, missing the api/ path segment, instead of routing through the shared request helper like every other command. upload_file/download_file bypassed the shared HTTP session entirely (via bare requests.post), skipping session-level retry/proxy/User-Agent configuration. get_file_info performed a full-bucket findfiles scan 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 empty A8_* 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 fallback API_URL also embedded a redundant /api/ (producing a double /api/ and 404s), and ConfigBuilder bypassed .env file loading entirely, unlike load_config(). A shell/process-level A8_CONFIG_ENV now also reliably takes precedence over a conflicting value merely defined in .env.
  • Bulk async harvest raised KeyError on a per-task url key and ignored each task's own egress_info in 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. Both url and urls per-task keys are now accepted, and per-task egress_info is 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-parsing ValidationError was 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_check reported stale tool-capability counts (drifted from the actual registered tool list) and always reported 0 latency 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 destructive a8_delete_org/a8_delete_partner_sso_config tools now reject a blank/whitespace-only org_name before 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 marked partial: True instead — a partial row is never filtered out by include_inactive=False, so a fetch failure can no longer masquerade as a genuinely idle user. OrgManagementAPI.get_session_report() also now strictly validates the documented MM-DD-YYYY date format (previously a loose length/dash-count check silently accepted ISO YYYY-MM-DD dates 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 cyclic get_org_children response could produce duplicate rows. All org-tree traversals (list_users_recursive, get_org_usage_report, get_user_usage_report, get_org_tree, and the move_users.py/find_user_orgs.py scripts) now share a single, consistently cycle-guarded and deduplicated traversal implementation.
  • a8_list_users_recursive was missing its own parameter-validation error handling, so an invalid max_depth surfaced 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_users now 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; the org/org_name field 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. The org_name field on returned rows is now the full path for any sub-org.
  • OrgManagementAPI.get_org_tree() / a8_get_org_tree() — fetched each node's org_id/user count by bare name; now uses the full path internally (the nested tree's own org_name field 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_name is 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 at DEBUG level, giving no signal that the returned data was incomplete. These failures are now logged at WARNING, 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) dropped org_id — the flattening step only copied org_name/current_users/depth from each tree node, silently omitting the org_id field get_org_tree() provides. Now included.

Added

  • org_id on every get_org_tree() nodea8_get_org_tree()/OrgManagementAPI.get_org_tree() now include a stable org_id per node, in addition to org_name and current_users. org_id is 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 the categories parameter's structure and values; previously invalid categories were silently accepted and forwarded to the API.

Changed

  • Retired the Lebanon egress location — no longer accepted as a valid egress_region/egress_info.name value.
  • Removed the EGRESS_LOCATIONS module-level constant from silo_sdk and silo_sdk.browsing. Use BrowsingAPI.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_block with enable/disable values (previously documented as adblock with block/allow, which the API rejects).
  • Corrected the domain blocklist policy type name to domain_block (previously domain_deny) across SDK docstrings, MCP tool help, and the browsing reference docs.
  • The default User-Agent header 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 reported silo-sdk/0.10.0).

Changed

  • Clarified the browser_chrome: seamless constraint: 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_profile fields timezone (IANA name) and languages (Accept-Language string), in addition to os and browser.

[0.10.2] - 2026-05-07

Added

  • Setup script (setup_venv.py --dev) now verifies mcp and silo_mcp imports 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.yaml lists 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.toml dependency pins corrected for mcp and fastapi extras.

[0.10.1] - 2026-05-05

Changed

  • Package rename: mcp_serversilo_mcp — the MCP server package is now silo_mcp/. The CLI entry point is silo-mcp (was authentic8-mcp). Install with pip install "silo-sdk[mcp-server]".
  • Packaging fixes: wheel now correctly includes silo_mcp/ and all sub-packages.

Fixed

  • Replaced aiohttp with httpx for async HTTP to resolve a dependency conflict.
  • Windows compatibility fix for cleanup.py and test path handling.

[0.10.0] - 2026-05-05

Added

  • Egress location detailsget_egress_locations(include_details=True) returns enriched per-city metadata including connectivity, availability, and protocol instead of plain name strings. Backward-compatible: default remains include_details=False.
  • EGRESS_LOCATION_DETAILS — new static dict in silo_sdk/browsing/isolation_api.py mapping 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 accepts include_details: bool = True (enriched output by default for LLM callers). Existing callers using include_details=False get the prior flat-string behavior.
  • TOR protocol per locationprotocol field populated for all cities: Sydney (["direct", "tor"]), Moncks Corner SC (["tor"] only), all others (["direct"]). Confirmed via live API testing.

Fixed

  • Availability values correctedVALID_AVAILABILITY_TYPES in egress_validation.py changed from {"private", "shared"} to {"private", "public"} per the Harvester API documentation (April 2026, p.4). "shared" was never a valid backend value; any caller passing availability="shared" would have silently received no egress results from the backend. EGRESS_LOCATION_DETAILS, Categories TypedDict, 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.
  • protocol field not exposedget_egress_locations(include_details=True) previously extracted connectivity and availability from EGRESS_LOCATION_DETAILS but silently dropped protocol. All three fields are now returned.

Changed

  • a8_list_egress_locations MCP tool default changed from returning flat strings to returning enriched dicts (include_details=True). Callers that need flat strings must now pass include_details=False explicitly.

[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; enhanced a8_health_check with 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_type in a8_extract_logs, idempotency hints in a8_add_user/a8_create_org, optional bucket_id in a8_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 scriptscripts/validate_log_schemas.py validates LOG_TYPE_FIELDS against real extracted log data; reports extra fields, empty types, and coverage summary; exit code 1 when gaps found (CI-friendly)
  • Encrypted log exampleexamples/decrypt_logs.py demonstrates the full ENC log extract-then-decrypt workflow with per-type breakdown and optional JSON output
  • Private key templateexamples/pvtkey.txt.example documents the pvtkey.txt format 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 CLI
  • tests/test_log_stats.py (66 tests) — all 11 analysis functions, formatters, and file loading
  • tests/test_mcp_logging_tools.py (27 tests) — MCP retry-on-lock behavior and exception handling
  • tests/test_export_logs_csv.py (28 tests) — CSV enhancement flags and resume state
  • Log parsing utilities — New silo_sdk/logging/log_utils.py module 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 scriptscripts/extract_all_logs.py extracts multiple log types sequentially with configurable pauses and automatic retry on backend lock contention
  • Log analytics scriptscripts/log_stats.py with 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-locka8_extract_logs gains retry_on_lock, retry_delay, and max_retries parameters for automatic lock contention handling
  • CSV export enhancementsscripts/export_logs.py gains --expand-json, --expand-nested, and --parse-egress flags for richer CSV output with dot-notation column expansion
  • NEXUS log type — Added NEXUS to VALID_LOG_TYPES and LOG_TYPE_FIELDS for Nexus AI conversation events (fields: conversation_id, message_type, org_name, toolbox_name, etc.)
  • ENC wrapper schema — Added ENC to LOG_TYPE_FIELDS with 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 (default False).
  • 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 (default True), active_only (default False). 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 filtering
  • scripts/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 hierarchy
  • get_user_usage_report() — Collect per-user session statistics across an org hierarchy

Changed

  • Log type field schemas synced with logtype_headers.ini v1.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_TYPES count — 25 → 26 (added NEXUS)
  • LOG_TYPE_FIELDS count — 24 → 26 (added NEXUS and ENC)
  • get_session_report() extended — Added optional org_id, user_id, and hierarchy parameters. org_id/org_name are mutually exclusive; user_id/username are mutually exclusive. hierarchy param enables subtree reporting (undocumented API feature).
  • MCP or None normalization — All optional string parameters in silo_mcp/tools/orgs.py now use param or None to prevent empty-string validation errors when AI assistants pass "" for omitted optional params.
  • a8_download_file and a8_download_harvest_result MCP toolsoutput_path parameter is now optional. When omitted:
  • a8_download_file saves to temp directory using the original filename from Silo storage metadata (falls back to file ID if metadata unavailable)
  • a8_download_harvest_result saves to temp directory as harvest_<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 (default True). Returns a flat list with org, 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": [...]}. candidates is 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_file content parameter — Accepts an optional content: str as an alternative to file_path. Provide exactly one of file_path or content; providing neither or both raises ValueError.
  • run-mcp.sh now appends stderr to /tmp/mcp-server.log for post-disconnect diagnosis.

[0.8.7] - 2026-04-01

Fixed

  • org.update — API returns flat dict; SDK expected list → now checks dict first
  • delete_partner_sso_config — API returns {"deleted":1,"status":1} dict; SDK expected int → fixed type check
  • get_session_report — API error responses embedded in result dict were silently returned as success → now raises OrgManagementAPIError
  • delete_harvest_task — result extracted from wrong response index → fixed to use _extract_api_result
  • upload_file — omitted path param 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 on POST /getfile/; confirmed via live test that /getfile/ accepts multipart/form-data only (id + auth fields); all three corrected
  • Postman harvest task wire formatCreate Asset Collection Task and FLOW — Create Visual Harvest Task had task_type/urls at command level and params as dicts; corrected to request_type inside task_params with {name, value} array format
  • docs/maintenance/api-recommendations.md — corrected session_report wire command from org.session_report to session_report; documented that include_children is silently ignored and :hierarchy is 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 as a8_update_context MCP tool
  • extract_logs() include_suborgs parameter — optional bool; when True includes log records from child organizations; omitted from request when not provided
  • find_harvest_task() finished parameter — optional bool; when True filters to completed tasks only, when False filters to in-progress tasks only; omitted from request when not provided. Also exposed on a8_find_harvest_task MCP tool

Changed

  • MCP a8_upload_file, a8_find_files, a8_list_files — docstrings clarify that bucket_id is 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 from silo_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.pyformat_table() and to_csv() shared by scripts.
  • silo_sdk/utils/org.pywalk_org_tree() (BFS generator) and print_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 users
  • scripts/batch_user_provisioning.py — bulk user creation from CSV with dry-run
  • scripts/analyze_log_storage.py — log type availability and sequence info
  • scripts/compare_org_configs.py — proxy policy / user count drift across org hierarchy
  • examples/batch_operations_demo.py — bulk context creation with per-item error capture, retry, and cleanup summary.
  • MCP tests: tests/test_mcp_credentials.py and tests/test_mcp_tools.py (skipped automatically when mcp package is not installed).
  • tests/test_utils_formatting.py and tests/test_utils_org.py.

Changed

  • All 39 MCP tools now translate SDK exceptions to MCP-friendly messages: ValidationError / ConfigurationErrorValueError; SiloErrorRuntimeError.
  • 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.py and scripts/session_report.py — table output now uses shared format_table() from silo_sdk.utils.formatting.
  • CI matrix — dropped Python 3.9 (EOL October 2025); minimum is now Python 3.10. Updated requires-python = ">=3.10".
  • black upgraded >=26.3.1. Pre-commit rev synced.

Fixed

  • tests/test_api_tag_on_static_methodcallable(staticmethod) guard for Python 3.9 (now skip-pathed via sys.version_info; no longer breaks on 3.10+ either).
  • tests/test_import_error — changed mock.patch(string) to mock.patch.object(module) for reliable behaviour across Python 3.10–3.13.
  • tests/test_mcp_tools.py — added pytest.importorskip("mcp") guard so CI does not fail when the mcp package (in [mcp-server] extra) is not installed.
  • Postman collection flow assertions — corrected Log Pipeline and Context Lifecycle flows: get_log_info (non-existent command) → extractlog probe; result array → result.logs array; AUTH,SESSION multi-type → single AUTH for this token.
  • docs/getting-started/authentication.md and docs/concepts/tokens.md — removed get_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 via extractlog probe (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. Accepts browse_context_id plus any combination of context_data, max_uses, expires, name, and enabled. Only supplied fields are updated; omitted fields are unchanged. Registered in command_registry.py.
  • extract_logs() include_suborgs parameter — optional bool that, when True, includes log records from child organizations in the extraction result. Omitted from the request when not provided (no change to existing behavior).
  • single harvest task type — added to VALID_TASK_TYPES. Single-URL non-recursive fetch; uses wget_params; available from all egress locations (unlike asset).
  • VALID_ASSET_EGRESS_LOCATIONS — new constant in egress_validation.py listing the six datacenter locations valid for asset tasks: Singapore, Dubai, Frankfurt, Sao Paulo, Johannesburg, New York City / New York, NY.
  • Three missing egress locations added to VALID_EGRESS_LOCATIONS for 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, and test_user_management_api.py. Total: 1,124 tests, 99.28% coverage.
  • Postman collection v0.8.5session_report request corrected, new Update Context request added to Browsing Isolation section, include_suborgs shown in Extract Logs example.

Changed

  • get_user() signatureusername and email are 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") or api.get_user(email="u@x.com").
  • get_session_report() signatureinclude_children parameter removed. Confirmed via live API testing this was never a real API parameter and was silently ignored by the server. The method now accepts org_name, start_date, end_date, and username.
  • Asset task egress validationcreate_harvest_task(task_type="asset", ...) now raises ValidationError if the supplied egress_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_LOCATIONS reorganised — list sorted consistently within regions; three previously missing locations added.

Fixed

  • scripts/session_report.py — removed --no-children CLI flag and include_children kwarg that was silently ignored by the API.
  • scripts/move_users.py and examples/user_demo.pyget_user() calls updated from positional argument to keyword argument (username=) to match the new signature.
  • silo_mcp/tools/orgs.py a8_get_session_report — removed include_children from the MCP tool signature.

Breaking changes

  • get_user(username) positional call → use get_user(username=username) or get_user(email=email). Passing both raises ValidationError.
  • get_session_report(include_children=...) → remove the argument; it no longer exists.
  • create_harvest_task(task_type="asset", egress_info={"name": "Tokyo"}) → raises ValidationError. Asset tasks must use one of the six valid datacenter locations.

[0.8.4] - 2026-03-16

Added

  • update_partner_sso_config — new OrgManagementAPI method 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 — new OrgManagementAPI method to permanently delete a Partner SSO configuration, its partner user, and the org itself. Irreversible.
  • Both new methods registered in command_registry.py and 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 an import-metadata subcommand 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 by manage_sso.py and the a8_parse_saml_metadata MCP tool.
  • a8_parse_saml_metadata MCP 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_settings missing, list_harvest_tasks missing, get_log_info missing) and resolved gaps (proxy policy write ops confirmed as policy_proxies.set/add/delete).
  • 20 new unit tests for SSO methods (1055 total).

Changed

  • create_partner_sso_config / get_partner_sso_config docstrings — 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_config docstring 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, and vanity_url as optional here vs required for SSO.
  • get_log_sequence_info() reimplemented — the get_log_info command is not recognized by the ext API. Now implemented as a CONVENIENCE method that probes via extractlog(start_seq=0, limit=1, type=AUTH). Returns min_seq, is_more, next_seq; max_seq and total_logs are explicitly None (not available via ext API).
  • extract_logs() bug fixed — was failing to unwrap the {"result": {...}} response envelope, causing logs to always be empty and is_more/next_seq to be None. The fix adds a single unwrap step consistent with how all other API modules handle responses.
  • export_logs.py fixedshow_info() updated to display earliest seq / is_more / next_seq instead of the unavailable min_seq/max_seq/range. Default start_seq logic now uses or 0 to gracefully fall back when no logs are found at the probe point.
  • a8_create_org MCP tool description updated to match corrected create_org() docstring.
  • a8_extract_logs MCP tool — corrected Returns (removed fabricated total_count; documents actual logs/is_more/next_seq fields).
  • a8_get_log_sequence_info MCP tool — updated to reflect probe-based implementation; max_seq/total_logs documented as None.
  • create_harvest_task docstring — corrected wire key names (wget_params/vid_params not asset_params/video_params); full wire format example now shows correct task_params nesting.
  • download_file docstring — added Note documenting POST /getfile/ endpoint with form-data id+auth (not a command-array request).
  • create_context docstring — added Note about GET /ctx/ shorthand with response= parameter; updated create_ctx_url to cross-reference.
  • create_partner_sso_config docstringparent_org_name clarified as required in practice.
  • docs/concepts/wire-protocol.md — new Non-Command-Array Endpoints section documenting POST /getfile/, POST /putfile/, and GET /ctx/ with response= parameter table.
  • MCP a8_create_harvest_task — warns against invalid task_type values (screenshot, pdf, mhtml); a8_create_partner_sso_config clarifies parent_org_name requirement.
  • Postman collection updated: 60 standalone requests now have real sanitized API response examples; harvest task wire format corrected; Download File corrected to POST /getfile/; Upload File form field names corrected (name/path); GET /ctx Shorthand updated with response=url parameter.
  • scripts/README.mdupdate_collection_examples.py documented with usage examples, SSO org reset procedure, and required env vars.
  • Version bumped to 0.8.4 across pyproject.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 from OrgManagementAPI — the org.update_settings command is not recognized by the ext API. Org settings (session limits, MFA, download policies) are not configurable via the API. a8_update_org_settings MCP tool removed.
  • list_harvest_tasks() removed from HarvesterAPI — the list_harvest_tasks command is not recognized by the ext API. Use find_harvest_task(task_id) for point lookup, or extract HARVEST log type entries to find task IDs. a8_list_harvest_tasks MCP tool removed.
  • settings param removed from create_org() — was unverified and unused.

Added (proxy policy management — confirmed real API commands)

  • get_proxy_policy() — new OrgManagementAPI method using policy_proxies.get
  • set_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 with proxies_deleted and proxies_not_found arrays
  • 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); port and type optional (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: seamless only available for non-catchall users; catchall users are always forced to minimal (Ribbon Mode)
  • egress_region validation added to BrowsingAPI.create_context() — validates against VALID_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 for silo_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 Report requests; List Tasks / Update Org Settings removed (commands do not exist)
  • ctx_url env var added to all four Postman environment files — enables multi-environment GET /ctx testing without hardcoded production URLs
  • proxy_name env 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 nested result.error — captures errors that HTTP 200 responses previously hid
  • Eng environment support in update_collection_examples.py: A8_CONFIG_ENV=eng uses .env.eng overrides; env-specific state files (.collection_update_state.eng.json); Newman env URLs derived from A8_API_URL; 57/60 standalone requests updated against eng
  • dest_path changed 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_url optional here but required for SSO; org hierarchy root documented; SCIM context added
  • browser_chrome policy type added to BrowsingAPI class docstring (3 values: standard, seamless, minimal); Seamless UI auth constraint documented
  • add_proxy_policy docstring — clarifies multiple proxies can be added in a single call
  • delete_proxy_policy docstring — 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.py and isolation_api.py: Asia-Pacific, Africa & Middle East, Central & South America
  • docs/maintenance/api-recommendations.md expanded with: proxy policy resolution, list_harvest_tasks confirmed 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_context fixed: "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 (not sso_config_id, which doesn't exist)
  • Auto-generated test_org_name and test_sso_org_name in pre-request scripts
  • Proxy policy and org.update_settings tests made graceful for permission-limited accounts
  • GET /ctx shorthand URL corrected (removed erroneous /api/ prefix)
  • Find Files — Date Range request added demonstrating file_type, :created_before, :created_after filter 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_uses placement
  • HarvesterAPI.create_harvest_task: valid task_type values, task_paramsvis_params mapping
  • LogExtractionAPI.extract_logs: log_types list→comma-joined string, date→epoch transforms
  • OrgManagementAPI.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 to BrowsingAPI, HarvesterAPI, and LogExtractionAPI class docstrings so mkdocstrings renders VALID_TASK_TYPES, VALID_VISUAL_PARAMS, VALID_LOG_TYPES as structured tables in the MkDocs API reference
  • MkDocs nav: new "Postman & Newman" section linking to the Newman testing guide

Fixed

  • .mcp.json pointed to outdated repository path; corrected.
  • MCP server mcp-server extras not installed; pip install -e ".[mcp-server]" now required for the MCP server to start (installs mcp, fastapi, uvicorn)
  • Wire format example in developer docs showed incorrect "data": {…} nesting; corrected to flat params
  • Wire format example in docs/concepts/wire-protocol.md showed wrong listusers structure
  • docs/getting-started/authentication.md: API_URL example had erroneous /api/ suffix
  • docs/getting-started/quickstart.md: misleading task_type="screenshot"task_type="visual"
  • docs/concepts/async.md: task_type="screenshot"task_type="visual"
  • docs/getting-started/quickstart.md and 13 other files: stale v0.8.1/v0.8.2 version references updated to v0.8.3
  • User-Agent string defaults updated from silo-sdk/0.8.1silo-sdk/0.8.3 in api_client.py, config.py, config_builder.py, config/default.json, config/production.json
  • silo_mcp/__init__.py version bumped to 0.8.3

Changed

  • Postman environment defaults updated: org_name maps to A8_TOP_ORG (not A8_ORG_VANITY_URL)
  • Postman collection info.version set to 0.8.3
  • Developer tooling documentation expanded: flat-param rule, create_context exception, org vs org_name param distinction, A8_TOP_ORG vs A8_ORG_VANITY_URL distinction

[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_apis to silo_sdk
  • Exception base class renamed from A8APIError to SiloError
  • A8APIError kept 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.yml moved to .gitlab/ci.yml for 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/ to Documentation/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.yml reusable 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 decryption
  • standard_decrypt_chunked() — 32 MB streaming for large files
  • decrypt_video_file() — atomic write with temp file
  • load_private_keys(), decrypt_log_entry(), decrypt_logs()
  • Private key file format: name=value pairs in pvtkey.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_after parameter 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 support
  • key_manager.py — CLI for managing private keys and API tokens
  • bulk_create_contexts.py — bulk browsing context creation from URL list
  • session_report.py — organization session report generator
  • file_manager.py — file storage CLI (list, search, download, info)
  • scheduled_visual_harvest.py — daily visual captures with run_after scheduling
  • decrypt_files.py — batch log file decryption
  • move_users.py — user migration between organizations
  • SDK demos in examples/:
  • user_demo.py, org_demo.py, log_demo.py, decrypt_demo.py
  • browsing_demo.py, file_demo.py, config_demo.py
  • harvest_demo_sync.py, harvest_demo_async.py
  • CONTRIBUTING.md with development workflow
  • .env.example template with all A8_* environment variables
  • Unified Postman API Reference Collection (100% SDK coverage)

Changed

  • examples/ renamed to sdk_examples/ for clarity (reverted to examples/ 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 random with secrets for 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_tag decorator 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-requests added 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 A8APIError with 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 with base/, 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