{"id":20595384,"url":"https://github.com/adobe-type-tools/pyots","last_synced_at":"2026-05-31T07:01:02.268Z","repository":{"id":45175297,"uuid":"261601566","full_name":"adobe-type-tools/pyots","owner":"adobe-type-tools","description":"Python bindings for OpenType Sanitizer via Python C Extension","archived":false,"fork":false,"pushed_at":"2026-05-31T04:48:25.000Z","size":158,"stargazers_count":12,"open_issues_count":2,"forks_count":1,"subscribers_count":6,"default_branch":"main","last_synced_at":"2026-05-31T05:09:53.327Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adobe-type-tools.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-05-05T23:01:10.000Z","updated_at":"2026-05-30T22:24:34.000Z","dependencies_parsed_at":"2022-07-22T11:17:39.375Z","dependency_job_id":"0e8cd191-486a-4c1e-b2f7-86ca18546e4f","html_url":"https://github.com/adobe-type-tools/pyots","commit_stats":{"total_commits":55,"total_committers":3,"mean_commits":"18.333333333333332","dds":0.4363636363636364,"last_synced_commit":"88695ef6a99a7b9e0e56fbd5b12a52096e419988"},"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/adobe-type-tools/pyots","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adobe-type-tools%2Fpyots","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adobe-type-tools%2Fpyots/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adobe-type-tools%2Fpyots/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adobe-type-tools%2Fpyots/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adobe-type-tools","download_url":"https://codeload.github.com/adobe-type-tools/pyots/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adobe-type-tools%2Fpyots/sbom","scorecard":{"id":167122,"data":{"date":"2025-08-11","repo":{"name":"github.com/adobe-type-tools/pyots","commit":"51aee4d14081ec1806d3e74d65446638dbac8dd6"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5.2,"checks":[{"name":"Code-Review","score":5,"reason":"Found 2/4 approved changesets -- score normalized to 5","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":6,"reason":"8 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 6","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/adobe-type-tools/pyots/codeql.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql.yml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/adobe-type-tools/pyots/codeql.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql.yml:36: update your workflow using https://app.stepsecurity.io/secureworkflow/adobe-type-tools/pyots/codeql.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql.yml:39: update your workflow using https://app.stepsecurity.io/secureworkflow/adobe-type-tools/pyots/codeql.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:31: update your workflow using https://app.stepsecurity.io/secureworkflow/adobe-type-tools/pyots/release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:36: update your workflow using https://app.stepsecurity.io/secureworkflow/adobe-type-tools/pyots/release.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:41: update your workflow using https://app.stepsecurity.io/secureworkflow/adobe-type-tools/pyots/release.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:45: update your workflow using https://app.stepsecurity.io/secureworkflow/adobe-type-tools/pyots/release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:63: update your workflow using https://app.stepsecurity.io/secureworkflow/adobe-type-tools/pyots/release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:96: update your workflow using https://app.stepsecurity.io/secureworkflow/adobe-type-tools/pyots/release.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:108: update your workflow using https://app.stepsecurity.io/secureworkflow/adobe-type-tools/pyots/release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/run_tests.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/adobe-type-tools/pyots/run_tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/run_tests.yml:31: update your workflow using https://app.stepsecurity.io/secureworkflow/adobe-type-tools/pyots/run_tests.yml/main?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/run_tests.yml:42","Warn: pipCommand not pinned by hash: .github/workflows/run_tests.yml:43","Warn: pipCommand not pinned by hash: .github/workflows/run_tests.yml:44","Warn: pipCommand not pinned by hash: .github/workflows/run_tests.yml:45","Warn: pipCommand not pinned by hash: .github/workflows/run_tests.yml:59","Info:   0 out of  10 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   4 third-party GitHubAction dependencies pinned","Info:   0 out of   5 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Info: jobLevel 'actions' permission set to 'read': .github/workflows/codeql.yml:16","Info: jobLevel 'contents' permission set to 'read': .github/workflows/codeql.yml:17","Warn: jobLevel 'contents' permission set to 'write': .github/workflows/release.yml:80","Warn: no topLevel permission defined: .github/workflows/codeql.yml:1","Warn: no topLevel permission defined: .github/workflows/release.yml:1","Warn: no topLevel permission defined: .github/workflows/run_tests.yml:1"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: BSD 3-Clause \"New\" or \"Revised\" License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/release.yml:70"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact 9.2.0.post2 not signed: https://api.github.com/repos/adobe-type-tools/pyots/releases/186753182","Warn: release artifact 9.1.0 not signed: https://api.github.com/repos/adobe-type-tools/pyots/releases/108276196","Warn: release artifact 9.0.0 not signed: https://api.github.com/repos/adobe-type-tools/pyots/releases/77309248","Warn: release artifact 8.2.2 not signed: https://api.github.com/repos/adobe-type-tools/pyots/releases/77042014","Warn: release artifact 8.2.1 not signed: https://api.github.com/repos/adobe-type-tools/pyots/releases/56329113","Warn: release artifact 9.2.0.post2 does not have provenance: https://api.github.com/repos/adobe-type-tools/pyots/releases/186753182","Warn: release artifact 9.1.0 does not have provenance: https://api.github.com/repos/adobe-type-tools/pyots/releases/108276196","Warn: release artifact 9.0.0 does not have provenance: https://api.github.com/repos/adobe-type-tools/pyots/releases/77309248","Warn: release artifact 8.2.2 does not have provenance: https://api.github.com/repos/adobe-type-tools/pyots/releases/77042014","Warn: release artifact 8.2.1 does not have provenance: https://api.github.com/repos/adobe-type-tools/pyots/releases/56329113"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2024-48 / GHSA-fj7x-q9j7-g6q6"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":9,"reason":"SAST tool detected but not run on all commits","details":["Info: SAST configuration detected: CodeQL","Warn: 20 commits out of 28 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-16T15:13:14.937Z","repository_id":45175297,"created_at":"2025-08-16T15:13:14.937Z","updated_at":"2025-08-16T15:13:14.937Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33722156,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-16T08:12:51.053Z","updated_at":"2026-05-31T07:01:02.262Z","avatar_url":"https://github.com/adobe-type-tools.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pyots (PYthon OT Sanitizer)\n\n[![Run Tests](https://github.com/adobe-type-tools/pyots/actions/workflows/run_tests.yml/badge.svg)](https://github.com/adobe-type-tools/pyots/actions/workflows/run_tests.yml) [![Build Python Wheels](https://github.com/adobe-type-tools/pyots/actions/workflows/release.yml/badge.svg)](https://github.com/adobe-type-tools/pyots/actions/workflows/release.yml)\n\n![PyPI](https://img.shields.io/pypi/v/pyots) [![PyPI](https://img.shields.io/pypi/pyversions/pyots)](https://pypi.org/project/pyots/)\n\n![macOS](https://img.shields.io/badge/-macOS-lightgrey) ![ubuntu](https://img.shields.io/badge/-ubuntu-lightgrey)\n\nPython wrapper for [OpenType Sanitizer](https://github.com/khaledhosny/ots), also known as just \"OTS\". It is similar to and partially based on [ots-python](https://github.com/googlefonts/ots-python), but builds OTS as a Python C Extension (instead of as an executable and calling through `subprocess` as ots-python does).\n\n**NOTE:** Although this package is similar to **ots-python**, it is _not_ a drop-in replacement for it, as the Python API is different.\n\n## Requirements\nThe project builds `pip`-installable wheels for Python 3.10, 3.11, 3.12, 3.13, or 3.14 under Mac or Linux. It is possible this project will build and run with other Pythons and other operating systems, but it has only been tested with the listed configurations.\n\n## Installation with `pip`\nIf you just want to _use_ `pyots`, you can simply run `python -m pip install -U pyots` (in one of the supported platforms/Python versions) which will install pre-built, compiled, ready-to-use Python wheels. Then you can skip down to the [Use](#Use) section.\n\n## Installation/setup for developing `pyots`\nIf you'd like to tinker with the `pyots` code, you will want to get your local setup ready:\n - clone this repo\n - run `python setup.py download` to download the OTS source (which is _not included_ in this project). You can modify the `version` value in [`setup.cfg`](./setup.cfg) under `[download]` to specify a different version of OTS. You'll also need to change the `sha256` hash value that corresponds to the OTS tar.xz package. Note that this scheme has some limitations: OTS sources older than 8.1.3 might not build correctly since they used different build systems. Also, versions newer than the one specified in this repo might require adjustments in order to build correctly. What can we say, we're dependent on `ots`...\n - to build and install `pyots` after downloading OTS, you can run `python setup.py install` or `python -m pip install .`\n - while iterating changes, you will want to delete the temporary `build` and `src/ots/build` folders.\n\n## Testing\nThere is a test suite defined for exercising the Python extension. It makes use (and assumes the presence of) the downloaded OTS library source's test font data in `src/ots` so ensure you have run `python setup.py download` and have the `ots` folder under `src`. Invoke the tests with `python -m pytest`.\n\nIf you wish to run tests comparing results from `ots-python` against `pyots`, be sure to `python -m pip install opentype-sanitizer` first, otherwise that set of tests will be skipped.\n\n## Use\nSimplest case:\n```python\nimport pyots\nresult = pyots.sanitize('/path/to/font/file.ttf')\n```\n\n`result` is an `OTSResult` object with 3 attributes:\n - `sanitized` Boolean indicating whether the file was successfully sanitized\n - `modified` Boolean indicating whether the file was modified* during sanitization\n - `messages` Tuple of message strings generated during sanitization (may be empty)\n\n* **Note:** currently the back-end OTS code can modify fonts that are successfully sanitized, even when no changes are performed. Thus `modified` can sometimes be True when `sanitized` is True. Usually the modification is only to the modification date and related checksums. Thus, it might be possible to devise a better detection of modification, i.e. ignoring `head.modified` and other inconsequential modifications, but that was out-of-scope for this work.\n\n### Example: sanitizing a folder of font files\n```python\n# sanitize a folder of fonts. Print messages for any that were not successfully sanitized.\nimport pyots\nfrom pathlib import Path\n\nfor filename in Path(\"src/ots/tests/fonts/good\").rglob(\"*\"):\n    result = pyots.sanitize(filename.absolute())\n    if not result.sanitized:\n        print(f'{filename}:\\n{\", \".join([m for m in result.messages])}')\n```\n\n### Options for `sanitize()`\n - Specify keyword `output=\u003cpath_to_output_file\u003e` to the `sanitize()` command and the sanitized file will be saved to that location\n - Use `quiet=True` for `sanitize()` to suppress messages\n - Specify `font_index=\u003cindex_in_TTC\u003e` when sanitizing a Collection (OTC/TTC) file and you want to sanitize only a particular index within the Collection (otherwise all will be sanitized per OTS's default behavior)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadobe-type-tools%2Fpyots","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadobe-type-tools%2Fpyots","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadobe-type-tools%2Fpyots/lists"}