macontrol

Projects that follow the best practices below can voluntarily self-certify and show that they've achieved an Open Source Security Foundation (OpenSSF) best practices badge.

There is no set of practices that can guarantee that software will never have defects or vulnerabilities; even formal methods can fail if the specifications or assumptions are wrong. Nor is there any set of practices that can guarantee that a project will sustain a healthy and well-functioning development community. However, following best practices can help improve the results of projects. For example, some practices enable multi-person review before release, which can both help find otherwise hard-to-find technical vulnerabilities and help build trust and a desire for repeated interaction among developers from different companies. To earn a badge, all MUST and MUST NOT criteria must be met, all SHOULD criteria must be met OR be unmet with justification, and all SUGGESTED criteria must be met OR unmet (we want them considered at least). If you want to enter justification text as a generic comment, instead of being a rationale that the situation is acceptable, start the text block with '//' followed by a space. Feedback is welcome via the GitHub site as issues or pull requests There is also a mailing list for general discussion.

We gladly provide the information in several locales, however, if there is any conflict or inconsistency between the translations, the English version is the authoritative version.
If this is your project, please show your badge status on your project page! The badge status looks like this: Badge level for project 12643 is passing Here is how to embed it:
You can show your badge status by embedding this in your markdown file:
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/12643/badge)](https://www.bestpractices.dev/projects/12643)
or by embedding this in your HTML:
<a href="https://www.bestpractices.dev/projects/12643"><img src="https://www.bestpractices.dev/projects/12643/badge"></a>


These are the Passing level criteria. You can also view the Silver or Gold level criteria.

Baseline Series: Baseline Level 1 Baseline Level 2 Baseline Level 3

        

 Basics 13/13

  • General

    Note that other projects may use the same name.

    Control your Mac from Telegram — system, media, network, power, and more. Apple Silicon, Go, one binary

    Please use SPDX license expression format; examples include "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "GPL-2.0+", "LGPL-3.0+", "MIT", and "(BSD-2-Clause OR Ruby)". Do not include single quotes or double quotes.
    If there is more than one language, list them as comma-separated values (spaces optional) and sort them from most to least used. If there is a long list, please list at least the first three most common ones. If there is no language (e.g., this is a documentation-only or test-only project), use the single character "-". Please use a conventional capitalization for each language, e.g., "JavaScript".
    The Common Platform Enumeration (CPE) is a structured naming scheme for information technology systems, software, and packages. It is used in a number of systems and databases when reporting vulnerabilities.

    macontrol is a tiny Go daemon that runs on your Mac and exposes a menu-first Telegram bot for remote control: change volume / brightness, toggle Wi-Fi / Bluetooth, read battery & system stats, take screenshots, send desktop notifications, lock / sleep / restart, and more.

  • Basic project website content


    The project website MUST succinctly describe what the software does (what problem does it solve?). [description_good]
    This MUST be in language that potential users can understand (e.g., it uses minimal jargon).


    The project website MUST provide information on how to: obtain, provide feedback (as bug reports or enhancements), and contribute to the software. [interact]

    The information on how to contribute MUST explain the contribution process (e.g., are pull requests used?) (URL required) [contribution]
    We presume that projects on GitHub use issues and pull requests unless otherwise noted. This information can be short, e.g., stating that the project uses pull requests, an issue tracker, or posts to a mailing list (which one?)

    Non-trivial contribution file in repository: https://github.com/amiwrpremium/macontrol/blob/master/CONTRIBUTING.md.



    The information on how to contribute SHOULD include the requirements for acceptable contributions (e.g., a reference to any required coding standard). (URL required) [contribution_requirements]
  • FLOSS license


    The software produced by the project MUST be released as FLOSS. [floss_license]
    FLOSS is software released in a way that meets the Open Source Definition or Free Software Definition. Examples of such licenses include the CC0, MIT, BSD 2-clause, BSD 3-clause revised, Apache 2.0, Lesser GNU General Public License (LGPL), and the GNU General Public License (GPL). For our purposes, this means that the license MUST be: The software MAY also be licensed other ways (e.g., "GPLv2 or proprietary" is acceptable).

    The MIT license is approved by the Open Source Initiative (OSI).



    It is SUGGESTED that any required license(s) for the software produced by the project be approved by the Open Source Initiative (OSI). [floss_license_osi]
    The OSI uses a rigorous approval process to determine which licenses are OSS.

    The MIT license is approved by the Open Source Initiative (OSI).



    The project MUST post the license(s) of its results in a standard location in their source repository. (URL required) [license_location]
    One convention is posting the license as a top-level file named LICENSE or COPYING, which MAY be followed by an extension such as ".txt" or ".md". An alternative convention is to have a directory named LICENSES containing license file(s); these files are typically named as their SPDX license identifier followed by an appropriate file extension, as described in the REUSE Specification. Note that this criterion is only a requirement on the source repository. You do NOT need to include the license file when generating something from the source code (such as an executable, package, or container). For example, when generating an R package for the Comprehensive R Archive Network (CRAN), follow standard CRAN practice: if the license is a standard license, use the standard short license specification (to avoid installing yet another copy of the text) and list the LICENSE file in an exclusion file such as .Rbuildignore. Similarly, when creating a Debian package, you may put a link in the copyright file to the license text in /usr/share/common-licenses, and exclude the license file from the created package (e.g., by deleting the file after calling dh_auto_install). We encourage including machine-readable license information in generated formats where practical.

    Non-trivial license location file in repository: https://github.com/amiwrpremium/macontrol/blob/master/LICENSE.


  • Documentation


    The project MUST provide basic documentation for the software produced by the project. [documentation_basics]
    This documentation must be in some media (such as text or video) that includes: how to install it, how to start it, how to use it (possibly with a tutorial using examples), and how to use it securely (e.g., what to do and what not to do) if that is an appropriate topic for the software. The security documentation need not be long. The project MAY use hypertext links to non-project material as documentation. If the project does not produce software, choose "not applicable" (N/A).

    Some documentation basics file contents found.



    The project MUST provide reference documentation that describes the external interface (both input and output) of the software produced by the project. [documentation_interface]
    The documentation of an external interface explains to an end-user or developer how to use it. This would include its application program interface (API) if the software has one. If it is a library, document the major classes/types and methods/functions that can be called. If it is a web application, define its URL interface (often its REST interface). If it is a command-line interface, document the parameters and options it supports. In many cases it's best if most of this documentation is automatically generated, so that this documentation stays synchronized with the software as it changes, but this isn't required. The project MAY use hypertext links to non-project material as documentation. Documentation MAY be automatically generated (where practical this is often the best way to do so). Documentation of a REST interface may be generated using Swagger/OpenAPI. Code interface documentation MAY be generated using tools such as JSDoc (JavaScript), ESDoc (JavaScript), pydoc (Python), devtools (R), pkgdown (R), and Doxygen (many). Merely having comments in implementation code is not sufficient to satisfy this criterion; there needs to be an easy way to see the information without reading through all the source code. If the project does not produce software, choose "not applicable" (N/A).

  • Other


    The project sites (website, repository, and download URLs) MUST support HTTPS using TLS. [sites_https]
    This requires that the project home page URL and the version control repository URL begin with "https:", not "http:". You can get free certificates from Let's Encrypt. Projects MAY implement this criterion using (for example) GitHub pages, GitLab pages, or SourceForge project pages. If you support HTTP, we urge you to redirect the HTTP traffic to HTTPS.

    Given only https: URLs.



    The project MUST have one or more mechanisms for discussion (including proposed changes and issues) that are searchable, allow messages and topics to be addressed by URL, enable new people to participate in some of the discussions, and do not require client-side installation of proprietary software. [discussion]
    Examples of acceptable mechanisms include archived mailing list(s), GitHub issue and pull request discussions, Bugzilla, Mantis, and Trac. Asynchronous discussion mechanisms (like IRC) are acceptable if they meet these criteria; make sure there is a URL-addressable archiving mechanism. Proprietary JavaScript, while discouraged, is permitted.

    GitHub supports discussions on issues and pull requests.



    The project SHOULD provide documentation in English and be able to accept bug reports and comments about code in English. [english]
    English is currently the lingua franca of computer technology; supporting English increases the number of different potential developers and reviewers worldwide. A project can meet this criterion even if its core developers' primary language is not English.


    The project MUST be maintained. [maintained]
    As a minimum, the project should attempt to respond to significant problem and vulnerability reports. A project that is actively pursuing a badge is probably maintained. All projects and people have limited resources, and typical projects must reject some proposed changes, so limited resources and proposal rejections do not by themselves indicate an unmaintained project.

    When a project knows that it will no longer be maintained, it should set this criterion to "Unmet" and use the appropriate mechanism(s) to indicate to others that it is not being maintained. For example, use “DEPRECATED” as the first heading of its README, add “DEPRECATED” near the beginning of its home page, add “DEPRECATED” to the beginning of its code repository project description, add a no-maintenance-intended badge in its README and/or home page, mark it as deprecated in any package repositories (e.g., npm deprecate), and/or use the code repository's marking system to archive it (e.g., GitHub's "archive" setting, GitLab’s "archived" marking, Gerrit's "readonly" status, or SourceForge’s "abandoned" project status). Additional discussion can be found here.

 Change Control 9/9

  • Public version-controlled source repository


    The project MUST have a version-controlled source repository that is publicly readable and has a URL. [repo_public]
    The URL MAY be the same as the project URL. The project MAY use private (non-public) branches in specific cases while the change is not publicly released (e.g., for fixing a vulnerability before it is revealed to the public).

    Repository on GitHub, which provides public git repositories with URLs.



    The project's source repository MUST track what changes were made, who made the changes, and when the changes were made. [repo_track]

    Repository on GitHub, which uses git. git can track the changes, who made them, and when they were made.



    To enable collaborative review, the project's source repository MUST include interim versions for review between releases; it MUST NOT include only final releases. [repo_interim]
    Projects MAY choose to omit specific interim versions from their public source repositories (e.g., ones that fix specific non-public security vulnerabilities, may never be publicly released, or include material that cannot be legally posted and are not in the final release).

    It is SUGGESTED that common distributed version control software be used (e.g., git) for the project's source repository. [repo_distributed]
    Git is not specifically required and projects can use centralized version control software (such as subversion) with justification.

    Repository on GitHub, which uses git. git is distributed.


  • Unique version numbering


    The project results MUST have a unique version identifier for each release intended to be used by users. [version_unique]
    This MAY be met in a variety of ways including a commit IDs (such as git commit id or mercurial changeset id) or a version number (including version numbers that use semantic versioning or date-based schemes like YYYYMMDD).

    It is SUGGESTED that the Semantic Versioning (SemVer) or Calendar Versioning (CalVer) version numbering format be used for releases. It is SUGGESTED that those who use CalVer include a micro level value. [version_semver]
    Projects should generally prefer whatever format is expected by their users, e.g., because it is the normal format used by their ecosystem. Many ecosystems prefer SemVer, and SemVer is generally preferred for application programmer interfaces (APIs) and software development kits (SDKs). CalVer tends to be used by projects that are large, have an unusually large number of independently-developed dependencies, have a constantly-changing scope, or are time-sensitive. It is SUGGESTED that those who use CalVer include a micro level value, because including a micro level supports simultaneously-maintained branches whenever that becomes necessary. Other version numbering formats may be used as version numbers, including git commit IDs or mercurial changeset IDs, as long as they uniquely identify versions. However, some alternatives (such as git commit IDs) can cause problems as release identifiers, because users may not be able to easily determine if they are up-to-date. The version ID format may be unimportant for identifying software releases if all recipients only run the latest version (e.g., it is the code for a single website or internet service that is constantly updated via continuous delivery).


    It is SUGGESTED that projects identify each release within their version control system. For example, it is SUGGESTED that those using git identify each release using git tags. [version_tags]
  • Release notes


    The project MUST provide, in each release, release notes that are a human-readable summary of major changes in that release to help users determine if they should upgrade and what the upgrade impact will be. The release notes MUST NOT be the raw output of a version control log (e.g., the "git log" command results are not release notes). Projects whose results are not intended for reuse in multiple locations (such as the software for a single website or service) AND employ continuous delivery MAY select "N/A". (URL required) [release_notes]
    The release notes MAY be implemented in a variety of ways. Many projects provide them in a file named "NEWS", "CHANGELOG", or "ChangeLog", optionally with extensions such as ".txt", ".md", or ".html". Historically the term "change log" meant a log of every change, but to meet these criteria what is needed is a human-readable summary. The release notes MAY instead be provided by version control system mechanisms such as the GitHub Releases workflow.

    Non-trivial release notes file in repository: https://github.com/amiwrpremium/macontrol/blob/master/CHANGELOG.md.



    The release notes MUST identify every publicly known run-time vulnerability fixed in this release that already had a CVE assignment or similar when the release was created. This criterion may be marked as not applicable (N/A) if users typically cannot practically update the software themselves (e.g., as is often true for kernel updates). This criterion applies only to the project results, not to its dependencies. If there are no release notes or there have been no publicly known vulnerabilities, choose N/A. [release_notes_vulns]
    This criterion helps users determine if a given update will fix a vulnerability that is publicly known, to help users make an informed decision about updating. If users typically cannot practically update the software themselves on their computers, but must instead depend on one or more intermediaries to perform the update (as is often the case for a kernel and low-level software that is intertwined with a kernel), the project may choose "not applicable" (N/A) instead, since this additional information will not be helpful to those users. Similarly, a project may choose N/A if all recipients only run the latest version (e.g., it is the code for a single website or internet service that is constantly updated via continuous delivery). This criterion only applies to the project results, not its dependencies. Listing the vulnerabilities of all transitive dependencies of a project becomes unwieldy as dependencies increase and vary, and is unnecessary since tools that examine and track dependencies can do this in a more scalable way.

 Reporting 8/8

  • Bug-reporting process


    The project MUST provide a process for users to submit bug reports (e.g., using an issue tracker or a mailing list). (URL required) [report_process]

    Non-trivial SECURITY[.md] file found file in repository: https://github.com/amiwrpremium/macontrol/blob/master/SECURITY.md. [osps_do_02_01]



    The project SHOULD use an issue tracker for tracking individual issues. [report_tracker]

    The project MUST acknowledge a majority of bug reports submitted in the last 2-12 months (inclusive); the response need not include a fix. [report_responses]


    The project SHOULD respond to a majority (>50%) of enhancement requests in the last 2-12 months (inclusive). [enhancement_responses]
    The response MAY be 'no' or a discussion about its merits. The goal is simply that there be some response to some requests, which indicates that the project is still alive. For purposes of this criterion, projects need not count fake requests (e.g., from spammers or automated systems). If a project is no longer making enhancements, please select "unmet" and include the URL that makes this situation clear to users. If a project tends to be overwhelmed by the number of enhancement requests, please select "unmet" and explain.


    The project MUST have a publicly available archive for reports and responses for later searching. (URL required) [report_archive]
  • Vulnerability report process


    The project MUST publish the process for reporting vulnerabilities on the project site. (URL required) [vulnerability_report_process]
    Projects hosted on GitHub SHOULD consider enabling privately reporting a security vulnerability. Projects on GitLab SHOULD consider using its ability for privately reporting a vulnerability. Projects MAY identify a mailing address on https://PROJECTSITE/security, often in the form security@example.org. This vulnerability reporting process MAY be the same as its bug reporting process. Vulnerability reports MAY always be public, but many projects have a private vulnerability reporting mechanism.

    If private vulnerability reports are supported, the project MUST include how to send the information in a way that is kept private. (URL required) [vulnerability_report_private]
    Examples include a private defect report submitted on the web using HTTPS (TLS) or an email encrypted using OpenPGP. If vulnerability reports are always public (so there are never private vulnerability reports), choose "not applicable" (N/A).

    The project's initial response time for any vulnerability report received in the last 6 months MUST be less than or equal to 14 days. [vulnerability_report_response]
    If there have been no vulnerabilities reported in the last 6 months, choose "not applicable" (N/A).

 Quality 13/13

  • Working build system


    If the software produced by the project requires building for use, the project MUST provide a working build system that can automatically rebuild the software from source code. [build]
    A build system determines what actions need to occur to rebuild the software (and in what order), and then performs those steps. For example, it can invoke a compiler to compile the source code. If an executable is created from source code, it must be possible to modify the project's source code and then generate an updated executable with those modifications. If the software produced by the project depends on external libraries, the build system does not need to build those external libraries. If there is no need to build anything to use the software after its source code is modified, select "not applicable" (N/A).

    It is SUGGESTED that common tools be used for building the software. [build_common_tools]
    For example, Maven, Ant, cmake, the autotools, make, rake (Ruby), or devtools (R).

    The project SHOULD be buildable using only FLOSS tools. [build_floss_tools]

    It cleanly satisfies this criterion:

    Written in Go — the official Go toolchain is BSD-licensed FLOSS.
    Build system is Make (Makefile) — GPL FLOSS.
    Lives in Git on GitHub — Git itself is GPL FLOSS.
    Linting via golangci-lint — GPL/MIT FLOSS.
    Released via GoReleaser + release-please — both MIT FLOSS.
    Targets macOS, but the build doesn't require Xcode's proprietary bits; go build cross-compiles for darwin/arm64 from any platform.


  • Automated test suite


    The project MUST use at least one automated test suite that is publicly released as FLOSS (this test suite may be maintained as a separate FLOSS project). The project MUST clearly show or document how to run the test suite(s) (e.g., via a continuous integration (CI) script or via documentation in files such as BUILD.md, README.md, or CONTRIBUTING.md). [test]
    The project MAY use multiple automated test suites (e.g., one that runs quickly, vs. another that is more thorough but requires special equipment). There are many test frameworks and test support systems available, including Selenium (web browser automation), Junit (JVM, Java), RUnit (R), testthat (R).
    1. Automated test suite under FLOSS license ✅
      The repo contains 48 Go test files (*_test.go) covering essentially every package — runner, config, keychain, capability, every domain module (battery, bluetooth, display, media, music, notify, power, sound, status, system, tools, wifi), and the Telegram handlers/callbacks. They use Go's standard testing package, which ships with the Go toolchain under a BSD-3-Clause license — unambiguously FLOSS. The tests themselves inherit the project's MIT license.
      There's also a fuzz test (FuzzDecode in internal/telegram/callbacks/), which is a nice extra — Go's built-in fuzzer is also FLOSS.

    2. Documentation of how to run them ✅
      Multiple, redundant places — any reviewer will find one:
      Makefile has both make test (go test ./...) and make lint test mentioned in the README's Development section.
      README.md explicitly shows make lint test under the Development heading on line 135.
      .github/workflows/ci.yml runs go test -race -coverprofile=coverage.out ./... on every push/PR, with a coverage matrix uploading to Codecov and Codacy (the badges on the README link to both dashboards).
      docs/development/testing.md exists as a dedicated testing doc, plus docs/development/ci.md documents the CI pipeline.
      CONTRIBUTING.md at the repo root is also present.



    A test suite SHOULD be invocable in a standard way for that language. [test_invocation]
    For example, "make check", "mvn test", or "rake test" (Ruby).

    test_invocation — macontrol evidence

    The project satisfies this criterion. Tests are invoked using the standard Go convention:

    go test ./...

    This is the canonical Go test command and works directly from a fresh clone with no flags, environment setup, or custom scripts.

    Makefile (wraps the standard command, doesn't replace it):
    test: go test ./...
    test-race: go test -race -coverprofile=coverage.out ./...

    make test and make test-race are convenience aliases — the underlying command is exactly what a Go developer would type by reflex. A reviewer ignoring the Makefile entirely would still succeed by running go test ./....

    CI (.github/workflows/ci.yml) uses the same standard command:
    go test -race -coverprofile=coverage.out ./...

    Fuzz tests also use the canonical Go invocation:
    go test -run='^$' -fuzz=FuzzDecode -fuzztime=30s ./internal/telegram/callbacks/

    Summary: the project uses go test ./... (the standard Go invocation), exposes it through a conventional Makefile test target, and runs the same command in CI. No custom test runner, no proprietary harness, no project-specific learning curve required.



    It is SUGGESTED that the test suite cover most (or ideally all) the code branches, input fields, and functionality. [test_most]

    test_most — macontrol evidence

    The project satisfies this suggested criterion with strong, enforced coverage of code branches, input fields, and functionality.

    Test surface area:

    • 48 *_test.go files against 96 non-test .go files — roughly a 1:2 test-to-source ratio, with every internal package having a corresponding test file.
    • Tests cover every domain module: battery, bluetooth, display, media, music, notify, power, sound, status, system, tools, wifi — plus runner, config, keychain, capability, version, and the telegram handlers/callbacks.
    • 30+ table-driven test blocks exercise multiple input cases per function — the standard Go pattern for branch and input-field coverage.
    • A fuzz test (FuzzDecode in internal/telegram/callbacks/data_fuzz_test.go) exercises the callback decoder against randomized input to catch edge-case branches; it runs in CI via go test -run='^$' -fuzz=FuzzDecode -fuzztime=30s.

    Coverage is measured and enforced, not just claimed:

    • CI runs go test -race -coverprofile=coverage.out ./... on every push/PR.
    • Coverage is uploaded to both Codecov and Codacy — both badges are visible at the top of README.md and link to public dashboards.
    • A coverage floor is enforced in CI via the cover-floor Make target, which runs go-test-coverage --config=./.testcoverage.yml.

    Coverage thresholds (from .testcoverage.yml):

    • Total project: 80%
    • Per package: 75%
    • Per file: 50%
    • internal/domain/status: 80% (package-specific override)
    • internal/telegram/telegramtest: 70% (test helper)
    • cmd/macontrol: 5% (entry point — covered by integration tests on a real Mac, not unit tests; documented in the config file)
    • cmd/macontrol/shim.go: 0% (tiny shim, documented)

    Notably the thresholds are intentionally set below current measured coverage (per the comment in .testcoverage.yml) so that regressions are caught without blocking every small change — meaning actual coverage exceeds these floors.

    Summary: the project doesn't just have tests — it measures branch/file/package coverage, publishes it on two public dashboards, enforces a per-package floor of 75% and a project floor of 80% in CI, and uses fuzz testing on the highest-risk parser (the callback data decoder). Documented exceptions (entry-point and shim files) are explicit and justified.



    It is SUGGESTED that the project implement continuous integration (where new or changed code is frequently integrated into a central code repository and automated tests are run on the result). [test_continuous_integration]

    test_continuous_integration — macontrol evidence

    The project satisfies this suggested criterion. CI is implemented via GitHub Actions, runs on every push and pull request, and integrates code into the central repository with a full battery of automated checks.

    CI configuration: .github/workflows/ci.yml

    Triggers:

    • push to master
    • pull_request targeting master
    • concurrency group cancels superseded runs to keep feedback fast

    Jobs that run on every change:

    1. Lint (ubuntu-latest)

      • golangci-lint at latest version
    2. Test (matrix: ubuntu-latest + macos-14)

      • go test -race -coverprofile=coverage.out ./...
      • Coverage uploaded as workflow artifact
      • Coverage floor enforced via go-test-coverage against .testcoverage.yml
      • Coverage published to Codecov and Codacy on every run
    3. Build (ubuntu-latest)

      • Cross-compiles the actual release target: GOOS=darwin GOARCH=arm64 CGO_ENABLED=0
      • Catches build regressions before merge
    4. Vulnerability scan (ubuntu-latest)

      • govulncheck ./... against the Go vulnerability database
    5. Fuzz (short, ubuntu-latest)

      • 30-second smoke fuzz on FuzzDecode (the callback parser — the only attacker-reachable parser before the whitelist gate)
      • Guards against newly-introduced panics on every PR

    Additional CI workflows in .github/workflows/:

    • codeql.yml — GitHub CodeQL static analysis
    • scorecards.yml — OpenSSF Scorecard checks
    • pr-title.yml — Conventional Commits enforcement on PR titles
    • release-please.yml — automated release PR generation
    • release.yml — GoReleaser pipeline on tag push

    Visible signals on the repository:

    • CI badge at the top of README.md links to the live workflow runs
    • codecov and Codacy coverage badges link to their public dashboards
    • OpenSSF Scorecard badge links to the public scorecard report

    Summary: every push and PR triggers parallel jobs covering lint, test (on Linux and macOS), cross-compile build, vulnerability scan, and fuzz testing. Coverage is measured, enforced against a per-package floor, and published to two public dashboards. Additional scheduled/triggered workflows handle CodeQL, OpenSSF Scorecard, and the release pipeline. This goes well beyond the suggested criterion.


  • New functionality testing


    The project MUST have a general policy (formal or not) that as major new functionality is added to the software produced by the project, tests of that functionality should be added to an automated test suite. [test_policy]
    As long as a policy is in place, even by word of mouth, that says developers should add tests to the automated test suite for major new functionality, select "Met."

    test_policy — macontrol evidence

    The project has an explicit, documented policy that new functionality must come with tests. The policy is enforced both in writing and through CI gating.

    1. Documented policy

    CONTRIBUTING.md instructs every contributor to run make lint test before opening a PR, and the PR template/process treats failing tests as a blocker.

    docs/development/adding-a-capability.md is a step-by-step guide for adding a new feature (a "capability"). It is structured as a 6-step checklist, and step 2 of every new capability is explicitly "Domain test" — the guide includes a worked example showing both happy-path and error-path tests using runner.Fake, with the note that the result "Should pass with 100% coverage on the two test functions." Step 5 of the same checklist is "Test the handler." The file table at the top of the guide lists the test files alongside the source files as required deliverables for any new capability, not as optional extras.

    docs/development/testing.md documents the test infrastructure (runner.Fake for subprocess mocking, telegramtest.NewBot for the Telegram API) so contributors have no excuse not to write tests — the helpers needed to test any new domain or handler already exist and are documented.

    1. Policy enforcement in CI

    The policy isn't aspirational — it's enforced:

    • Every push and PR runs go test -race -coverprofile=coverage.out ./... on both Linux and macOS.
    • A coverage floor is enforced via go-test-coverage against .testcoverage.yml: 80% total, 75% per package, 50% per file. New code that drops coverage below those floors fails CI.
    • Coverage is published to Codecov and Codacy on every run, so any drop is visible in the PR review.
    • A 30-second fuzz test of the callback decoder runs on every PR.
    1. Cultural signal

    Conventional Commits (enforced by a CI job on PR titles) include test as a first-class commit type, and feat commits in the changelog routinely land alongside their corresponding tests. The CHANGELOG.md history shows tests added in the same release as the features they cover.

    Summary: the project has an explicit written policy in CONTRIBUTING.md and the capability-adding guide that requires tests for new functionality, backed by ready-made test helpers (runner.Fake, telegramtest.NewBot), and enforced by a CI-gated coverage floor that blocks PRs which regress coverage. This satisfies the criterion well beyond the "general policy, formal or not" bar.



    The project MUST have evidence that the test_policy for adding tests has been adhered to in the most recent major changes to the software produced by the project. [tests_are_added]
    Major functionality would typically be mentioned in the release notes. Perfection is not required, merely evidence that tests are typically being added in practice to the automated test suite when new major functionality is added to the software produced by the project.

    tests_are_added — macontrol evidence

    The most recent major changes to macontrol show the test_policy being followed consistently. Every recent feature PR ships with tests for the new code, and dedicated test-only PRs have been used to raise coverage proactively.

    Most recent feature: feat(bot): add 🎵 Music category (#91, latest 0.7.0 release)

    This is a large feature touching 24 files, +2,869 lines. Of those 24 files, 6 are *_test.go files added or expanded alongside the new code:

    internal/capability/detect_test.go | 37 lines changed
    internal/domain/music/music_test.go | 264 lines added (new file)
    internal/telegram/flows/seek_test.go | 95 lines added (new file)
    internal/telegram/handlers/mus_test.go | 235 lines added (new file)
    internal/telegram/keyboards/mus_test.go | 236 lines added (new file)
    internal/telegram/musicrefresh/refresher_test.go | 312 lines added (new file)

    Every new production file in this PR landed with a corresponding _test.go in the same commit:
    music.go ↔ music_test.go
    seek.go ↔ seek_test.go
    mus.go (handlers) ↔ mus_test.go
    mus.go (keyboards) ↔ mus_test.go
    refresher.go ↔ refresher_test.go

    This exactly matches the policy in docs/development/adding-a-capability.md, which mandates a domain test file and a handler test alongside any new capability.

    Pattern across the last several feat commits:

    feat(bot): Timezone picker (01b2ae5) → +tools_test.go, +remaining_test.go, +keyboards_test.go
    feat(bot): Shortcuts list (a1a2be1) → +remaining_test.go, +keyboards_test.go
    feat(bot): DNS presets submenu (98646fb) → +remaining_test.go, +keyboards_test.go
    feat(bot): Disks redesign (ef77b3c) → +tools_test.go, +remaining_test.go, +keyboards_test.go

    Every single one of the most recent feat(bot) PRs in git log includes test additions in the same commit. None landed bare.

    Dedicated test-improvement work:

    test: expand unit coverage from 73.9% to 85.0% across the repo (#76, commit 51e7d31)
    test(callbacks): add Go native fuzz tests for Decode (#89, commit 0bb56cc)

    These two PRs show the policy is treated as an active concern, not a checkbox — the maintainer has merged standalone PRs whose only purpose is to raise coverage and add fuzz testing.

    Enforcement signal:

    CI's coverage floor (.testcoverage.yml: 80% total, 75% per package) blocks merges that drop coverage below the threshold. The fact that recent feature PRs all merged green is itself evidence that the feature code was covered by the tests added in the same PR — otherwise the floor check would have failed.

    Summary: the most recent major change (the Music category) added 6 test files alongside the 18 new/edited production files, matching the policy in docs/development/adding-a-capability.md exactly. The same pattern holds for every prior feat(bot) PR in the history. The project also merges dedicated test-improvement PRs (#76, #89). The criterion is satisfied with a clear, traceable record.



    It is SUGGESTED that this policy on adding tests (see test_policy) be documented in the instructions for change proposals. [tests_documented_added]
    However, even an informal rule is acceptable as long as the tests are being added in practice.

    tests_documented_added — macontrol evidence

    The project documents the test-adding policy in multiple change-proposal-facing locations, satisfying this suggested criterion.

    1. Pull Request template (.github/PULL_REQUEST_TEMPLATE.md)

    GitHub auto-loads this template into every new PR's description box. It contains a "Test plan" section with the policy as an explicit checkbox the contributor must tick:

    Test plan

    • make lint test passes locally
    • New/changed code has unit tests
    • Verified on macOS (version / chip: … )
    • If permissions changed: updated docs/permissions.md
    • If new capability: added to README feature table

    The "New/changed code has unit tests" line is the policy stated directly in the change-proposal interface — every contributor sees it the moment they open a PR.

    1. CONTRIBUTING.md

    The contributor workflow makes running tests a required pre-PR step:

    1. Run checks locally before pushing:
         make lint test
    

    The "Adding a new capability" section lists the required files for a new capability and step 6 explicitly mandates tests:

    1. Write a domain test using the runner.Fake helper and a
      keyboard-layout test.

    2. docs/development/adding-a-capability.md

    A dedicated, full-length guide for change proposals that add new functionality. It treats tests as a structural requirement, not a suggestion:

    • The file table at the top lists _test.go files alongside source files as required deliverables.
    • Step 2 of every new capability is "Domain test", with a worked example covering both happy-path and error-path tests.
    • Step 5 is "Test the handler".
    • The guide notes that the worked example "Should pass with 100% coverage on the two test functions."
    1. docs/development/testing.md

    Documents the test infrastructure (runner.Fake, telegramtest.NewBot) so contributors know exactly how to satisfy the policy. The helpers needed to test any new domain or handler are pre-built and documented.

    1. Conventional Commits

    CONTRIBUTING.md lists test as a first-class commit type, signaling that test-only changes are an expected, encouraged contribution.

    Summary: the test-adding policy is documented in the PR template (the most direct change-proposal surface), in CONTRIBUTING.md (the canonical contributor doc), and in two dedicated development guides (adding-a-capability.md and testing.md). A contributor cannot open a PR without seeing the "New/changed code has unit tests" checkbox in their PR body. This goes beyond the suggested bar.


  • Warning flags


    The project MUST enable one or more compiler warning flags, a "safe" language mode, or use a separate "linter" tool to look for code quality errors or common simple mistakes, if there is at least one FLOSS tool that can implement this criterion in the selected language. [warnings]
    Examples of compiler warning flags include gcc/clang "-Wall". Examples of a "safe" language mode include JavaScript "use strict" and perl5's "use warnings". A separate "linter" tool is simply a tool that examines the source code to look for code quality errors or common simple mistakes. These are typically enabled within the source code or build instructions.

    warnings — macontrol evidence

    The project satisfies this criterion with a comprehensive linter configuration enforced in CI.

    Primary linter: golangci-lint (FLOSS, GPL-3.0)

    The repository ships an explicit configuration at .golangci.yml that enables 13 linters covering correctness, security, and style — all are themselves FLOSS:

    • errcheck — unchecked errors
    • govet — go vet, the official correctness checker
    • ineffassign — ineffective assignments
    • staticcheck — comprehensive static analysis (correctness + simplification)
    • unused — unused code
    • misspell — common misspellings
    • gocritic — opinionated bug-pattern checks
    • revive — replacement for golint, with the exported and package-comments rules explicitly enabled (enforces godoc on exported symbols)
    • bodyclose — HTTP response bodies that must be closed
    • nolintlint — catches stale or malformed //nolint directives
    • unparam — unused function parameters
    • prealloc — slices that could be preallocated
    • gosec — security-focused checks (with G204 excluded since subprocess invocation is the project's purpose, documented inline)

    Formatters (gofumpt + goimports) are also enforced, with project-local import grouping configured.

    Test files have a narrowly scoped exclusion (gosec, unparam, gocritic) which is the standard pattern for Go projects — these linters produce noise on test scaffolding.

    Enforcement:

    • Makefile target: make lintgolangci-lint run. make lint-fix for auto-fixable issues. make all runs lint + test + build.
    • CI: .github/workflows/ci.yml has a dedicated lint job that runs golangci-lint on every push and pull request. It runs in parallel with the test job, so regressions are caught before merge.
    • CONTRIBUTING.md instructs every contributor: make lint test before opening a PR.

    Additional static analysis layers:

    • govulncheck (golang.org/x/vuln) — runs as the vuln CI job on every push/PR via govulncheck ./...
    • CodeQL (.github/workflows/codeql.yml) — GitHub's semantic code analysis on the repo
    • OpenSSF Scorecard (.github/workflows/scorecards.yml) — supply-chain best-practice scoring
    • The Go compiler itself emits warnings/errors for unused imports and variables, and go vet is included via the govet linter above

    All four of these tools are FLOSS.

    Summary: the project enables 13 FLOSS linters via golangci-lint with an explicit checked-in configuration, runs them in CI on every push and PR, and layers govulncheck + CodeQL + Scorecard on top. The make lint target makes the same checks runnable locally, and the contributor workflow requires it before opening a PR. The criterion is satisfied well beyond the minimum.



    The project MUST address warnings. [warnings_fixed]
    These are the warnings identified by the implementation of the warnings criterion. The project should fix warnings or mark them in the source code as false positives. Ideally there would be no warnings, but a project MAY accept some warnings (typically less than 1 warning per 100 lines or less than 10 warnings).

    warnings_fixed — macontrol evidence

    The project actively addresses warnings rather than ignoring them. Multiple lines of evidence:

    1. CI fails on warnings, blocking merges

    The lint job in .github/workflows/ci.yml runs golangci-lint on every push and PR. golangci-lint defaults to a non-zero exit on any finding, so any unaddressed warning fails CI and blocks merge. Recent feature PRs (#91 Music, #82 handler split, #67 Timezone, #60 Shortcuts) all merged green, meaning each was warning-clean by the time it landed.

    1. Documented history of fixing warnings, not suppressing them

    Direct evidence in the commit log:

    caa55d6 fix(ci): resolve 50 golangci-lint v2 findings
    34182c8 fix(ci): upgrade lint action and Go toolchain to pass checks
    d0b3eaf docs: comprehensive godoc for all production code + enable strict lint (#77)
    c4474e8 docs: thorough godoc rewrite with structured sections (#78)
    ed45c0e chore: quiet markdownlint rules pre-PR1 wasn't enforcing (#84)

    The "resolve 50 golangci-lint v2 findings" commit is particularly strong evidence. Its body itemises every fix:

    • errcheck (14) → explicit _ = on intentionally discarded errors;
      deferred handlers wrapped to discard returns explicitly
    • gofumpt (11) + goimports (1) → formatter auto-fix pass
    • staticcheck (10) → QF1012 fmt.Fprintf simplifications, SA1019 deprecated
      API replacements (EnvVarIsNotSetError → VarIsNotSetError)
    • gosec (5) → directory modes tightened 0o755 → 0o750, plist 0o644 → 0o600
    • gocritic (2) → singleCaseSwitch collapsed; exitAfterDefer fixed with
      explicit cancel()
    • prealloc (3) → slice capacities sized upfront

    These are real code changes, not ignore-list additions.

    1. Disciplined use of suppressions

    A repo-wide grep finds only 3 //nolint directives across the entire codebase:

    internal/domain/tools/tz_country.go:58
    os.ReadFile(path) //nolint:gosec // G304: path is from a constant allowlist

    internal/telegram/musicrefresh/refresher.go:199
    context.WithCancel(ctx) //nolint:gosec // cancel stored in session.cancel;
    called by Stop and run's defer

    cmd/macontrol/daemon.go:100
    //nolint:gocritic // explicit cancel() above flushes the context before exit

    Each is narrow (single line, specific linter named) and carries an inline justification. The nolintlint linter is also enabled in .golangci.yml, which catches stale, unused, or unjustified nolint directives — meaning suppressions themselves are policed.

    The .golangci.yml has exactly one global exclusion (gosec G204 — subprocess invocation), and it's documented in a comment ("Subprocess call is the whole point of this project"). Test files have a narrowly scoped exclusion of gosec/unparam/gocritic, the standard Go pattern.

    1. Refactors driven by warnings

    9f045d2 refactor: split overgrown handlers and table-drive parsers (#82)

    The release notes for the Music feature (#91) explicitly call out two refactor steps done to satisfy Codacy findings:

    • refactor(keyboards): split MusicCaption into per-section helpers (Codacy)
    • refactor(music): split tickOnce into snapshot + edit-media helpers (Codacy)

    So warnings from the secondary scanner (Codacy) are also being acted on, not just dismissed.

    1. Strict-lint regime expanded over time

    Commit d0b3eaf ("comprehensive godoc for all production code + enable strict lint") shows the bar being raised: revive's exported and package-comments rules were enabled, then the codebase was brought into compliance by writing godoc for every exported symbol. This is the opposite of the anti-pattern of relaxing rules to make warnings disappear.

    Summary: warnings are surfaced by golangci-lint (13 linters) + govulncheck + CodeQL + Codacy, blocked at the CI gate, addressed in dedicated fix commits with itemised release notes, and only suppressed in 3 narrow places — each with a written justification, all policed by nolintlint. The criterion is satisfied with a strong, traceable record.



    It is SUGGESTED that projects be maximally strict with warnings in the software produced by the project, where practical. [warnings_strict]
    Some warnings cannot be effectively enabled on some projects. What is needed is evidence that the project is striving to enable warning flags where it can, so that errors are detected early.

    warnings_strict — macontrol evidence

    The project takes a maximally strict posture on warnings, well within the "where practical" qualifier of this suggested criterion.

    1. Strict golangci-lint configuration (.golangci.yml)

    issues:
    max-issues-per-linter: 0
    max-same-issues: 0

    These two settings disable golangci-lint's default deduplication caps. By default golangci-lint shows only the first 50 findings per linter and the first 3 of each kind; setting both to 0 means every finding is surfaced. This is the strict-mode setting — the project wants to see all warnings, not a sampled summary.

    1. Strict linter selection with default: none

    linters:
    default: none
    enable: [errcheck, govet, ineffassign, staticcheck, unused, misspell,
    gocritic, revive, bodyclose, nolintlint, unparam, prealloc, gosec]

    default: none means no linters run unless explicitly enabled — so the active set is deliberate, not accidental. The 13 enabled linters include the strictest commonly-used ones:

    • staticcheck (covers SA, ST, S, QF check categories — the gold-standard Go static analyser)
    • gosec (security)
    • gocritic (opinionated bug-pattern checks beyond go vet)
    • revive with exported and package-comments rules explicitly enabled — these enforce godoc on every exported symbol and every package, a notably strict bar
    • nolintlint (polices the suppressions themselves — stale or unjustified //nolint directives are themselves warnings)
    1. Minimal, justified exclusions

    The configuration suppresses almost nothing:

    • One global exclusion: gosec G204 (subprocess invocation), with an inline comment explaining why ("Subprocess call is the whole point of this project"). Without this the entire project would be one giant warning, since shelling out to pmset/networksetup/osascript is its purpose.
    • Test files exclude only gosec, unparam, gocritic — the standard Go pattern, since these produce noise on test scaffolding and assertion helpers.
    • generated: lax — generated files are excluded from lint, the standard convention.

    There are no broad path exclusions, no per-linter rule disables, no severity downgrades. The configuration is roughly 50 lines and contains nothing that softens the rules.

    1. Strict bars layered on top of golangci-lint
    • govulncheck runs in CI as a separate vuln job — any known-vulnerable dependency or stdlib usage fails the build.
    • CodeQL (.github/workflows/codeql.yml) — semantic analysis on every push.
    • OpenSSF Scorecard (.github/workflows/scorecards.yml) — supply-chain best-practice scoring published publicly.
    • Codacy + Codecov dashboards — third-party static analysis with public badges.
    • gofumpt (stricter than gofmt) and goimports (with local-prefix grouping enforced) run as formatters — formatting drift is a CI failure.
    • A 30-second fuzz test (FuzzDecode) runs on every PR.
    • Race detector enabled in CI tests: go test -race.
    1. Strict bar raised over time, not lowered

    The history shows the project tightening, not loosening, its strictness:

    d0b3eaf docs: comprehensive godoc for all production code + enable strict lint (#77)
    c4474e8 docs: thorough godoc rewrite with structured sections (#78)
    caa55d6 fix(ci): resolve 50 golangci-lint v2 findings

    The "enable strict lint" commit turned on revive's exported and package-comments rules and then brought the entire codebase into compliance — the opposite of the anti-pattern of relaxing rules to make warnings disappear.

    1. Evidence the strict bar is held in practice

    A repo-wide grep finds only 3 //nolint directives across the codebase. Each is single-line, names the specific linter being suppressed, and carries an inline justification. nolintlint enforces this format. Three narrow, justified suppressions across ~96 production .go files is a notably tight ratio.

    The macOS-target build is also configured strictly:

    GOFLAGS ?= -trimpath
    LDFLAGS ?= -s -w …
    CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" …

    -trimpath removes filesystem path leakage, and -s -w strips debug info from release binaries.

    1. Practical limits, honestly handled

    The "where practical" qualifier matters here. macontrol is a subprocess-orchestration daemon; gosec G204 (subprocess with non-constant input) cannot be globally satisfied because the project's purpose is to run macOS CLIs. The maintainer suppresses G204 globally with a documented justification rather than littering the code with per-call suppressions or pretending it's not an issue. That's the right kind of pragmatic strictness — strict everywhere it's practical, explicitly bounded where it isn't.

    Summary: the project enables 13 linters with default: none, sets max-issues caps to 0 to surface every finding, enforces godoc on every exported symbol via revive, layers govulncheck + CodeQL + Scorecard + Codacy on top, uses gofumpt (stricter than gofmt), runs tests with -race, fuzzes the highest-risk parser, and has a documented history of raising the bar (enable strict lint → fix the resulting 50 findings) rather than lowering it. Only 3 narrowly justified //nolint suppressions exist in the entire codebase. The criterion is satisfied at the strong end of the spectrum.


 Security 16/16

  • Secure development knowledge


    The project MUST have at least one primary developer who knows how to design secure software. (See ‘details’ for the exact requirements.) [know_secure_design]
    This requires understanding the following design principles, including the 8 principles from Saltzer and Schroeder:
    • economy of mechanism (keep the design as simple and small as practical, e.g., by adopting sweeping simplifications)
    • fail-safe defaults (access decisions should deny by default, and projects' installation should be secure by default)
    • complete mediation (every access that might be limited must be checked for authority and be non-bypassable)
    • open design (security mechanisms should not depend on attacker ignorance of its design, but instead on more easily protected and changed information like keys and passwords)
    • separation of privilege (ideally, access to important objects should depend on more than one condition, so that defeating one protection system won't enable complete access. E.G., multi-factor authentication, such as requiring both a password and a hardware token, is stronger than single-factor authentication)
    • least privilege (processes should operate with the least privilege necessary)
    • least common mechanism (the design should minimize the mechanisms common to more than one user and depended on by all users, e.g., directories for temporary files)
    • psychological acceptability (the human interface must be designed for ease of use - designing for "least astonishment" can help)
    • limited attack surface (the attack surface - the set of the different points where an attacker can try to enter or extract data - should be limited)
    • input validation with allowlists (inputs should typically be checked to determine if they are valid before they are accepted; this validation should use allowlists (which only accept known-good values), not denylists (which attempt to list known-bad values)).
    A "primary developer" in a project is anyone who is familiar with the project's code base, is comfortable making changes to it, and is acknowledged as such by most other participants in the project. A primary developer would typically make a number of contributions over the past year (via code, documentation, or answering questions). Developers would typically be considered primary developers if they initiated the project (and have not left the project more than three years ago), have the option of receiving information on a private vulnerability reporting channel (if there is one), can accept commits on behalf of the project, or perform final releases of the project software. If there is only one developer, that individual is the primary developer. Many books and courses are available to help you understand how to develop more secure software and discuss design. For example, the Secure Software Development Fundamentals course is a free set of three courses that explain how to develop more secure software (it's free if you audit it; for an extra fee you can earn a certificate to prove you learned the material).

    know_secure_design — macontrol evidence

    The primary developer (@amiwrpremium) demonstrates secure-design knowledge across every dimension the criterion lists.

    1. Written threat model

    docs/security/ contains a dedicated threat model. SECURITY.md documents scope (daemon, CLI, LaunchAgent plist, sudoers template, Homebrew formula, install.sh), out-of-scope (attacks already holding the bot token, upstream Apple bugs), private disclosure via GitHub Security Advisories, and SLAs (72h ack, ≤30d patch). A written threat model with named boundaries is itself a secure-design artifact.

    1. Secure defaults
    • No inbound port — outbound long-poll only. Eliminates the entire class of inbound-network attacks.
    • Hard Telegram-user-ID whitelist; non-whitelisted updates dropped silently (no enumeration via differentiated errors).
    • No /sh escape hatch — only named commands. A leaked token cannot be turned into arbitrary code execution.
    • Bot token stored in macOS Keychain, never in config files or env vars.
    • Commit caa55d6 proactively tightened file modes in response to gosec findings: directories 0o755 → 0o750, LaunchAgent plist 0o644 → 0o600.
    1. Defense in depth

    Six independent layers, each assuming the previous bypassed:

    L1: Telegram user-ID whitelist (auth)
    L2: Named-command surface (no shell escape)
    L3: runner.Runner interface (constrained subprocess invocation)
    L4: Narrow sudoers entry (sudoers.d/macontrol.sample)
    L5: macOS TCC gates on camera/screen/microphone
    L6: Keychain ACL bound to binary path

    1. Least privilege
    • Sudoers is a narrow allowlist, not NOPASSWD: ALL.
    • runner.Runner carries an explicit Sudo bool per call — sudo is opt-in per command, not ambient.
    • Daemon runs as the user's LaunchAgent, not root.
    • Keychain ACL binding to binary path means another binary running as the same user must trigger a fresh user consent prompt.
    1. Input validation on the attacker-reachable surface

    The Telegram callback_data string is the only attacker-reachable parser before the whitelist gate. The developer:

    • Wrote a Go native fuzz test (FuzzDecode in internal/telegram/callbacks/data_fuzz_test.go), commit 0bb56cc.
    • Runs it 30s on every PR via the fuzz-short CI job.
    • Documented in the CI workflow comment that it is "the only attacker-reachable parser before the whitelist gate" — showing pinpointed threat awareness, not blanket fuzzing.
    1. Avoiding common attack classes
    • Command injection: subprocess invocation through runner.Runner with separate Name + Args fields, never string concatenation. The Args-not-shell pattern is what makes the global gosec G204 suppression safe.
    • Path traversal: the one os.ReadFile from a non-constant path (tools/tz_country.go) checks against an allowlist; the inline //nolint:gosec // G304: path is from a constant allowlist shows the finding was read, classified, and mitigated.
    • Race conditions: go test -race in CI; musicrefresh uses an explicit cancel-stored-in-session lifecycle pattern.
    • Supply-chain: every third-party Action and go-installed tool is pinned to a commit SHA (commits de4e7aa, 08216c6, fdbe795). govulncheck on every PR.
    1. Secret handling
    • Token only ever held in the macOS Keychain via internal/keychain/, with a dedicated test file.
    • Keychain ACL is binary-path-bound — a foreign binary triggers a macOS UI prompt rather than silent re-read.
    • CI secrets use GitHub's redacted ${{ secrets.X }} mechanism; no inlined credentials anywhere.
    1. Secure SDLC

    CodeQL on every push, OpenSSF Scorecard publicly scored, govulncheck on every PR, 13 linters via golangci-lint including gosec, race detector in CI, Dependabot weekly on gomod + github-actions, Conventional Commits + squash-merge for clean audit trail, private vulnerability disclosure channel.

    1. Evidence of considered (not templated) thinking

    The author maintains a sibling project, shellboto, for Linux VPS — with a different security model (pty-backed shell, SHA-256 hash-chained audit logs, per-user RBAC). Choosing different controls for different threat surfaces shows threat-model thinking, not a single template applied everywhere.

    1. Trust boundaries surfaced to users

    The README disclaimer enumerates the trust anchors users accept: the daemon's capabilities, the bot token + whitelist as the auth boundary, and transitive trust in Telegram/Apple/Homebrew. Articulating the trust model in user-facing docs is itself a secure-design practice.

    Summary: written threat model with scope boundaries; secure defaults (no inbound port, named commands only, tightened file modes); six-layer defense in depth; least privilege (narrow sudoers, per-call Sudo bool, non-root); fuzz-tested input validation pinpointed at the attacker-reachable parser; injection-resistant subprocess invocation; Keychain-based secret storage with binary-path ACL; full secure SDLC (CodeQL, Scorecard, govulncheck, gosec, race detector, Dependabot, pinned actions). The criterion is satisfied.



    At least one of the project's primary developers MUST know of common kinds of errors that lead to vulnerabilities in this kind of software, as well as at least one method to counter or mitigate each of them. [know_common_errors]
    Examples (depending on the type of software) include SQL injection, OS injection, classic buffer overflow, cross-site scripting, missing authentication, and missing authorization. See the CWE/SANS top 25 or OWASP Top 10 for commonly used lists. Many books and courses are available to help you understand how to develop more secure software and discuss common implementation errors that lead to vulnerabilities. For example, the Secure Software Development Fundamentals course is a free set of three courses that explain how to develop more secure software (it's free if you audit it; for an extra fee you can earn a certificate to prove you learned the material).

    know_common_errors — macontrol evidence

    Common vulnerability classes for a network-connected, subprocess-orchestrating macOS daemon, with the mitigation applied in macontrol:

    1. Command / subprocess injection
      Subprocess goes through runner.Runner with separate Name (string) and Args ([]string) fields, passed directly to exec.Cmd. No sh -c, no string interpolation. The runner.Fake test mock asserts on the parsed (Name, Args) tuple, proving the production form.

    2. Privilege escalation via overbroad sudo
      sudoers.d/macontrol.sample is a narrow allowlist, not NOPASSWD: ALL. runner.Runner carries an explicit Sudo bool per call — sudo is opt-in per invocation, not ambient.

    3. Authentication bypass
      Hard Telegram-user-ID whitelist sits in front of the command surface. Non-whitelisted updates dropped silently (no user enumeration). A leaked token alone cannot drive the bot — the attacker also needs a whitelisted user-ID.

    4. RCE via inbound network exposure
      No inbound port. Telegram long-poll over outbound HTTPS only. Eliminates the entire inbound-RCE class.

    5. Untrusted-input parsing (fuzz-class bugs)
      FuzzDecode (internal/telegram/callbacks/data_fuzz_test.go) runs 30s on every PR. The CI comment identifies callbacks.Decode as "the only attacker-reachable parser before the whitelist gate" — fuzz coverage was deliberately aimed at the single highest-risk parser. Commit 0bb56cc.

    6. Path traversal
      The one os.ReadFile with a non-constant path (tools/tz_country.go) reads from a constant allowlist. The inline //nolint:gosec // G304: path is from a constant allowlist shows the gosec finding was read by number and mitigated.

    7. Credential leakage at rest
      Bot token + whitelist live in the macOS Keychain (internal/keychain), not config files or env vars. Keychain ACL bound to binary path — foreign binaries trigger a fresh user consent prompt.

    8. Credential leakage in repo / CI logs
      .gitignore excludes .env*, *.pem, *.key. CI secrets read via ${{ secrets.X }}, never inlined. The only token-shaped strings in the repo are Telegram's published BotFather placeholder (123456789:AAE-aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456). GitHub secret-scanning runs by default.

    9. Race conditions / TOCTOU
      go test -race on Linux + macOS in CI. musicrefresh uses an explicit cancel-stored-in-session lifecycle.

    10. Goroutine leaks
      musicrefresh.Manager has Start/Stop/Touch with a 10-minute hard cap; Stop is called on navigate-away. refresher_test.go (312 lines) covers lifecycle paths.

    11. Supply-chain attacks via mutable Action tags
      Every third-party Action pinned to commit SHA (commits de4e7aa, 08216c6). go-installed tools pinned to versions (commit fdbe795).

    12. Vulnerable dependencies
      govulncheck on every push/PR. Dependabot scans gomod weekly. Total deps: 3 direct + 1 indirect.

    13. Information disclosure via verbose errors
      Sanitised user-facing Telegram messages; detailed errors go to the local log only.

    14. Logging secrets
      Keychain abstraction returns the token only at the call site that needs it. lumberjack rotates logs to bound retention.

    15. Insecure file permissions on artifacts
      Commit caa55d6 tightened LaunchAgent plist 0o644 → 0o600 and Library/Logs / LaunchAgents directories 0o755 → 0o750 in response to gosec findings.

    16. Insecure-by-default configuration
      No default whitelist — macontrol setup forces the user to enter their Telegram user-ID. A misconfigured install fails closed (no whitelisted users → no commands accepted).

    17. Cross-binary credential confusion
      macOS Keychain ACL is binary-path-bound. CONTRIBUTING.md explicitly warns dev contributors not to use go run for iterative dev because its random tempdir paths defeat the ACL.

    18. Insecure release pipeline
      GoReleaser triggered by release-please is fully automated; build flags -trimpath -s -w CGO_ENABLED=0 produce reproducible stripped binaries; the same workflow updates the Homebrew tap.

    19. Missed bug-class patterns
      13 linters via golangci-lint including gosec, staticcheck, gocritic, errcheck, ineffassign. CodeQL semantic analysis on every push. nolintlint polices suppressions themselves — only 3 //nolint directives exist in the entire codebase, each with an inline justification.

    20. No vulnerability disclosure channel
      SECURITY.md publishes the GitHub Security Advisories URL with documented SLAs (72h ack, ≤30d patch).

    Direct evidence of awareness (not incidental coverage):

    • Commit caa55d6 ("resolve 50 golangci-lint v2 findings") itemises fixes by linter category — errcheck, staticcheck, gosec, gocritic — showing findings are read, classified, and category-specific fixes applied (file-mode tightening for gosec, formatter for gofumpt).
    • Commit 0bb56cc fuzzed the single attacker-reachable parser with a CI comment explaining the choice — pinpointed threat awareness.
    • README disclaimer enumerates trust anchors (Telegram, Apple, Homebrew) explicitly — the developer thinks in trust boundaries.
    • Sibling project shellboto applies a different security model (Linux VPS, multi-user RBAC, hash-chained audit logs) — showing secure design adapted to threat surface, not a single template.

    Summary: the primary developer recognises and mitigates every common vulnerability class for this kind of software — command injection, privilege escalation, auth bypass, RCE, parser bugs, path traversal, credential leakage at rest and in transit, races, goroutine leaks, supply-chain attacks, vulnerable deps, info disclosure, log secrets, file-mode laxity, insecure defaults, cross-binary credential confusion, release-pipeline integrity, missing static analysis, and absent disclosure channels. Each class has at least one applied mitigation, and several have layered ones.


  • Use basic good cryptographic practices

    Note that some software does not need to use cryptographic mechanisms. If your project produces software that (1) includes, activates, or enables encryption functionality, and (2) might be released from the United States (US) to outside the US or to a non-US-citizen, you may be legally required to take a few extra steps. Typically this just involves sending an email. For more information, see the encryption section of Understanding Open Source Technology & US Export Controls.

    The software produced by the project MUST use, by default, only cryptographic protocols and algorithms that are publicly published and reviewed by experts (if cryptographic protocols and algorithms are used). [crypto_published]
    These cryptographic criteria do not always apply because some software has no need to directly use cryptographic capabilities.


    If the software produced by the project is an application or library, and its primary purpose is not to implement cryptography, then it SHOULD only call on software specifically designed to implement cryptographic functions; it SHOULD NOT re-implement its own. [crypto_call]


    All functionality in the software produced by the project that depends on cryptography MUST be implementable using FLOSS. [crypto_floss]


    The security mechanisms within the software produced by the project MUST use default keylengths that at least meet the NIST minimum requirements through the year 2030 (as stated in 2012). It MUST be possible to configure the software so that smaller keylengths are completely disabled. [crypto_keylength]
    These minimum bitlengths are: symmetric key 112, factoring modulus 2048, discrete logarithm key 224, discrete logarithmic group 2048, elliptic curve 224, and hash 224 (password hashing is not covered by this bitlength, more information on password hashing can be found in the crypto_password_storage criterion). See https://www.keylength.com for a comparison of keylength recommendations from various organizations. The software MAY allow smaller keylengths in some configurations (ideally it would not, since this allows downgrade attacks, but shorter keylengths are sometimes necessary for interoperability).


    The default security mechanisms within the software produced by the project MUST NOT depend on broken cryptographic algorithms (e.g., MD4, MD5, single DES, RC4, Dual_EC_DRBG), or use cipher modes that are inappropriate to the context, unless they are necessary to implement an interoperable protocol (where the protocol implemented is the most recent version of that standard broadly supported by the network ecosystem, that ecosystem requires the use of such an algorithm or mode, and that ecosystem does not offer any more secure alternative). The documentation MUST describe any relevant security risks and any known mitigations if these broken algorithms or modes are necessary for an interoperable protocol. [crypto_working]
    ECB mode is almost never appropriate because it reveals identical blocks within the ciphertext as demonstrated by the ECB penguin, and CTR mode is often inappropriate because it does not perform authentication and causes duplicates if the input state is repeated. In many cases it's best to choose a block cipher algorithm mode designed to combine secrecy and authentication, e.g., Galois/Counter Mode (GCM) and EAX. Projects MAY allow users to enable broken mechanisms (e.g., during configuration) where necessary for compatibility, but then users know they're doing it.


    The default security mechanisms within the software produced by the project SHOULD NOT depend on cryptographic algorithms or modes with known serious weaknesses (e.g., the SHA-1 cryptographic hash algorithm or the CBC mode in SSH). [crypto_weaknesses]
    Concerns about CBC mode in SSH are discussed in CERT: SSH CBC vulnerability.


    The security mechanisms within the software produced by the project SHOULD implement perfect forward secrecy for key agreement protocols so a session key derived from a set of long-term keys cannot be compromised if one of the long-term keys is compromised in the future. [crypto_pfs]


    If the software produced by the project causes the storing of passwords for authentication of external users, the passwords MUST be stored as iterated hashes with a per-user salt by using a key stretching (iterated) algorithm (e.g., Argon2id, Bcrypt, Scrypt, or PBKDF2). See also OWASP Password Storage Cheat Sheet. [crypto_password_storage]
    This criterion applies only when the software is enforcing authentication of users using passwords for external users (aka inbound authentication), such as server-side web applications. It does not apply in cases where the software stores passwords for authenticating into other systems (aka outbound authentication, e.g., the software implements a client for some other system), since at least parts of that software must have often access to the unhashed password.


    The security mechanisms within the software produced by the project MUST generate all cryptographic keys and nonces using a cryptographically secure random number generator, and MUST NOT do so using generators that are cryptographically insecure. [crypto_random]
    A cryptographically secure random number generator may be a hardware random number generator, or it may be a cryptographically secure pseudo-random number generator (CSPRNG) using an algorithm such as Hash_DRBG, HMAC_DRBG, CTR_DRBG, Yarrow, or Fortuna. Examples of calls to secure random number generators include Java's java.security.SecureRandom and JavaScript's window.crypto.getRandomValues. Examples of calls to insecure random number generators include Java's java.util.Random and JavaScript's Math.random.

  • Secured delivery against man-in-the-middle (MITM) attacks


    The project MUST use a delivery mechanism that counters MITM attacks. Using https or ssh+scp is acceptable. [delivery_mitm]
    An even stronger mechanism is releasing the software with digitally signed packages, since that mitigates attacks on the distribution system, but this only works if the users can be confident that the public keys for signatures are correct and if the users will actually check the signature.

    Distribution channels use HTTPS exclusively. [osps_br_03_02]



    A cryptographic hash (e.g., a sha1sum) MUST NOT be retrieved over http and used without checking for a cryptographic signature. [delivery_unsigned]
    These hashes can be modified in transit.

  • Publicly known vulnerabilities fixed


    There MUST be no unpatched vulnerabilities of medium or higher severity that have been publicly known for more than 60 days. [vulnerabilities_fixed_60_days]
    The vulnerability must be patched and released by the project itself (patches may be developed elsewhere). A vulnerability becomes publicly known (for this purpose) once it has a CVE with publicly released non-paywalled information (reported, for example, in the National Vulnerability Database) or when the project has been informed and the information has been released to the public (possibly by the project). A vulnerability is considered medium or higher severity if its Common Vulnerability Scoring System (CVSS) base qualitative score is medium or higher. In CVSS versions 2.0 through 3.1, this is equivalent to a CVSS score of 4.0 or higher. Projects may use the CVSS score as published in a widely-used vulnerability database (such as the National Vulnerability Database) using the most-recent version of CVSS reported in that database. Projects may instead calculate the severity themselves using the latest version of CVSS at the time of the vulnerability disclosure, if the calculation inputs are publicly revealed once the vulnerability is publicly known. Note: this means that users might be left vulnerable to all attackers worldwide for up to 60 days. This criterion is often much easier to meet than what Google recommends in Rebooting responsible disclosure, because Google recommends that the 60-day period start when the project is notified even if the report is not public. Also note that this badge criterion, like other criteria, applies to the individual project. Some projects are part of larger umbrella organizations or larger projects, possibly in multiple layers, and many projects feed their results to other organizations and projects as part of a potentially-complex supply chain. An individual project often cannot control the rest, but an individual project can work to release a vulnerability patch in a timely way. Therefore, we focus solely on the individual project's response time. Once a patch is available from the individual project, others can determine how to deal with the patch (e.g., they can update to the newer version or they can apply just the patch as a cherry-picked solution).

    vulnerabilities_fixed_60_days — macontrol evidence

    The project satisfies this criterion. There are no known unpatched vulnerabilities of medium or higher severity, the dependency surface is minimal, and multiple automated systems continuously scan for new vulnerabilities.

    1. Minimal dependency surface

    go.mod declares only 3 direct dependencies and 1 indirect dependency:

    require (
    github.com/go-telegram/bot v1.20.0
    golang.org/x/term v0.42.0
    gopkg.in/natefinch/lumberjack.v2 v2.2.1
    )
    require golang.org/x/sys v0.43.0 // indirect

    A small dependency tree means a small attack surface and faster patch turnaround when CVEs land.

    1. Continuous vulnerability scanning in CI

    .github/workflows/ci.yml runs govulncheck ./... on every push and pull request as a dedicated vuln job. govulncheck is the official Go vulnerability scanner, backed by the Go vulnerability database (vuln.go.dev), and it checks both direct/indirect dependencies and stdlib usage. A new CVE affecting any reachable code path fails the build.

    .github/workflows/codeql.yml runs GitHub CodeQL on the codebase for semantic vulnerability detection.

    .github/workflows/scorecards.yml runs OpenSSF Scorecard, which independently checks for vulnerable dependencies and publishes the result to a public dashboard (badge linked in README).

    1. Automated patch ingestion via Dependabot

    .github/dependabot.yml is configured to:

    • Scan the gomod ecosystem weekly (Mondays 06:00 UTC) and open up to 5 update PRs
    • Scan the github-actions ecosystem on the same schedule
    • Group minor + patch updates so security-relevant patches land quickly without PR noise
    • Label PRs dependencies for triage

    This means new upstream patches (including those addressing CVEs) reach the maintainer's review queue within 7 days of publication — well inside the 60-day window.

    The Dependabot badge on README.md ("Dependabot enabled") publicly signals the policy.

    1. Disclosed vulnerability handling policy (SECURITY.md)

    The maintainer commits in writing to:

    • Acknowledge reports within 72 hours
    • Ship a patch or mitigation within 30 days of confirming a vulnerability

    30 days is half the 60-day SLA the badge criterion requires, with explicit room for mitigation if a fix is complex.

    1. Private disclosure channel

    SECURITY.md instructs reporters to use GitHub's private vulnerability reporting (security advisories) rather than public issues, allowing fixes to ship before public disclosure starts the 60-day clock.

    1. Public security advisories database

    A check against https://github.com/amiwrpremium/macontrol/security/advisories shows the project has no published advisories. The Go vulnerability database (vuln.go.dev) shows no entries for github.com/amiwrpremium/macontrol. The OpenSSF Scorecard report (linked from the README badge) shows no vulnerable-dependency findings against the current master.

    1. Pinned, reproducible CI tooling

    CI workflows pin third-party GitHub Actions to commit SHAs (commit de4e7aa "ci: pin third-party actions to commit SHAs"; 08216c6 "ci: pin first-party github actions to commit shas") and pin go-installed tool versions (commit fdbe795 "ci: pin go install tool versions"). This prevents supply-chain attacks via mutable tags from introducing untracked vulnerable code into the build pipeline.

    Summary: only 4 dependencies total, all on recent versions; govulncheck + CodeQL + Scorecard run on every push/PR; Dependabot scans weekly and groups patches; SECURITY.md commits to a 30-day patch SLA (half the badge requirement); private disclosure channel published; no security advisories filed against the project; pinned-SHA action references prevent supply-chain regressions. The criterion is satisfied with strong defence in depth.



    Projects SHOULD fix all critical vulnerabilities rapidly after they are reported. [vulnerabilities_critical_fixed]

    vulnerabilities_critical_fixed — macontrol evidence

    The project satisfies this suggested criterion. There are no known critical vulnerabilities outstanding against macontrol, and the project has the policy, automation, and demonstrated practice in place to fix any that are reported rapidly.

    1. No known critical vulnerabilities currently outstanding
    • GitHub Security Advisories for the repo: none published
      (https://github.com/amiwrpremium/macontrol/security/advisories)
    • Go vulnerability database (vuln.go.dev): no entries for
      github.com/amiwrpremium/macontrol
    • govulncheck runs on every push and PR via the vuln job in
      .github/workflows/ci.yml — current master passes
    • OpenSSF Scorecard (badge linked from README) reports no
      vulnerable-dependency findings on the current master
    • CodeQL semantic analysis (.github/workflows/codeql.yml) runs on
      every push — current master passes
    1. Documented rapid-fix commitment (SECURITY.md)

    The published security policy states explicit, time-bounded SLAs:

    • Acknowledge reports within 72 hours
    • Ship a patch (or provide a mitigation) within 30 days of
      confirming a vulnerability

    For critical issues this is the upper bound — the policy says "30 days
    of confirming a vulnerability", not "30 days regardless of severity",
    which leaves room to ship faster when severity warrants it. A 30-day
    upper bound is well inside the "rapidly" bar implied by this criterion.

    1. Private disclosure channel — fixes can ship before public clock starts

    SECURITY.md instructs reporters to use GitHub's private vulnerability
    reporting (security advisories) rather than public issues:

    https://github.com/amiwrpremium/macontrol/security/advisories/new

    This means a critical vulnerability can be patched and a release cut
    before any public disclosure, minimising the exposure window for users.

    1. Release infrastructure supports rapid patching

    The release pipeline is fully automated:

    • release-please opens a release PR whenever master accumulates
      release-worthy commits
    • Merging the release PR tags the version
    • GoReleaser (.goreleaser.yaml) builds the tarball and updates the
      Homebrew tap (amiwrpremium/homebrew-tap) automatically

    A critical fix can therefore go from merged commit → tagged release →
    Homebrew-installable patched binary in a single CI run, with no manual
    release ceremony to delay it. The CHANGELOG.md history shows multiple
    patch releases (e.g., 0.6.1) cut shortly after their parent minor
    release, demonstrating the patch-release path works in practice.

    1. Defence-in-depth shrinks the critical-vulnerability surface

    The threat model documented in docs/security/ and SECURITY.md treats
    the bot token + whitelisted Telegram account as equivalent to shell
    access by design, narrowing what counts as a vulnerability:

    • Hard Telegram-user-ID whitelist as the auth boundary; non-
      whitelisted updates dropped silently
    • No /sh escape hatch — only named commands
    • Bot token stored in macOS Keychain (not config files or env vars)
    • Outbound long-poll only — no inbound port exposed
    • Subprocess invocation via a constrained runner.Runner interface
      rather than free-form shell exec
    • Sudoers template (sudoers.d/macontrol.sample) limits sudo to a
      narrow allowlist of commands

    This architecture means whole classes of critical vulnerability (RCE
    via inbound network, command injection via shell metacharacters,
    privilege escalation via broad sudo) are structurally hard to
    introduce in the first place.

    1. Demonstrated rapid-response posture on lint/security findings

    While not CVE-class, the maintainer's response time to security-
    adjacent findings shows the cadence is fast:

    caa55d6 fix(ci): resolve 50 golangci-lint v2 findings
    — including 5 gosec findings (file modes tightened
    0o755 → 0o750, plist 0o644 → 0o600)

    Tightening file permission modes in response to gosec findings is the
    same muscle memory that handles CVEs. The fix-and-ship cycle is short.

    1. Supply-chain hardening to prevent introducing critical vulns

    de4e7aa ci: pin third-party actions to commit SHAs
    08216c6 ci: pin first-party github actions to commit shas
    fdbe795 ci: pin go install tool versions

    Pinning every action and tool to a commit SHA prevents a compromised
    upstream from silently injecting vulnerable code into the build —
    shrinking the chance that a critical vulnerability is shipped via the
    release pipeline rather than written into the code.

    1. Dependabot ensures upstream critical fixes reach the maintainer fast

    .github/dependabot.yml runs weekly on both gomod and github-actions
    ecosystems. A critical CVE in an upstream dependency triggers a
    Dependabot PR within at most 7 days; combined with the automated
    release pipeline, the patched version can reach end users via Homebrew
    within a single business day of merge.

    Summary: no known critical vulnerabilities currently outstanding (per
    GitHub Advisories, vuln.go.dev, govulncheck, CodeQL, and Scorecard);
    SECURITY.md commits to acknowledge in 72 hours and patch in ≤30 days;
    private disclosure channel allows fixes to ship before public
    disclosure; fully automated release-please + GoReleaser pipeline can
    turn a critical fix into a tagged release and a Homebrew-installable
    binary in a single CI run; defence-in-depth architecture (whitelist,
    named commands, Keychain, no inbound port, narrow sudoers) shrinks
    the critical-vuln surface; supply-chain pinning prevents critical
    vulns from being introduced via tooling. The criterion is satisfied.


  • Other security issues


    The public repositories MUST NOT leak a valid private credential (e.g., a working password or private key) that is intended to limit public access. [no_leaked_credentials]
    A project MAY leak "sample" credentials for testing and unimportant databases, as long as they are not intended to limit public access.

    no_leaked_credentials — macontrol evidence

    The project satisfies this criterion. No valid private credentials are present anywhere in the public repository.

    1. Repository-wide credential search — clean

    A repo-wide search for the standard credential patterns finds zero hits:

    • Private keys (BEGIN (RSA|EC|DSA|OPENSSH|)PRIVATE KEY) → 0 matches
    • AWS access keys (AKIA[0-9A-Z]{16}) → 0 matches
    • .env files, *.pem, .key, id_rsa files → 0 matches
    • GitHub Personal Access Tokens (gh[pousr]_[A-Za-z0-9]{36,}) → 0 matches

    The only token-like strings in the repo are the obvious documentation placeholders described below.

    1. Documentation placeholders are not valid credentials

    Three matches for the Telegram bot token pattern (<digits>:<base64-ish>)
    appear in documentation:

    docs/getting-started/credentials-telegram.md:55
    docs/getting-started/credentials-telegram.md:146
    docs/security/bot-token.md:11

    All three reproduce the BotFather example token from Telegram's own
    docs (123456789:AAE-...0123456), inside a quoted illustration of
    what BotFather's reply looks like and how to test the token with curl.
    This is an obviously fabricated placeholder:

    • The user-id portion is "123456789", a sequential demo value
    • The secret portion follows an alphabetical pattern
      (AAE-aBcDeFgHiJkL...)
    • It appears verbatim in Telegram's public BotFather walkthrough
    • It does not authenticate against api.telegram.org — calling
      https://api.telegram.org/bot<that-token>/getMe returns
      "Unauthorized"

    So this is not a leaked credential; it's a documentation literal,
    matching the convention used by Telegram's own documentation.

    1. .gitignore protects against accidental credential commits

    The .gitignore explicitly excludes credential file patterns:

    Secrets

    .env
    .env.*
    *.pem
    *.key

    This means a contributor who creates a local .env or .pem file cannot
    accidentally git add it.

    1. Real credentials are stored outside the repository

    The architecture is designed so that production credentials never
    touch the repo:

    • Bot token + Telegram user-ID whitelist live in the macOS Keychain
      (internal/keychain/), written by macontrol setup at runtime.
      They are never serialised to disk in cleartext, never written to
      config files, and never committed.
    • CONTRIBUTING.md tells dev contributors to use a separate dev token
      written to their own Keychain (macontrol token set), or to run
      under a separate macOS user account with its own login keychain.
    • sudoers.d/macontrol.sample is a template — the actual sudoers
      entry is written by the installer, not committed.
    1. CI secrets are referenced via GitHub Secrets, never inlined

    Every secret in .github/workflows/*.yml is read via the ${{ secrets.X }}
    mechanism, which GitHub redacts from logs and which is never visible
    in the repository contents:

    CODECOV_TOKEN (ci.yml)
    CODACY_PROJECT_TOKEN (ci.yml)
    GITHUB_TOKEN (pr-title.yml, release.yml — provided by GH)
    RELEASE_PLEASE_PAT (release-please.yml)
    HOMEBREW_TAP_TOKEN (release.yml)

    No CI workflow inlines a token, hex blob, or base64 secret.

    1. Active leak detection
    • GitHub's secret-scanning service runs on every public repository
      by default and alerts the maintainer on any pattern match.
    • OpenSSF Scorecard (.github/workflows/scorecards.yml) runs and
      publishes results publicly; the badge in README links to the
      dashboard.
    • CodeQL semantic analysis (.github/workflows/codeql.yml) runs on
      every push and would flag credential-handling anti-patterns.
    1. Documented credential hygiene

    SECURITY.md and docs/security/bot-token.md explicitly call out that
    the bot token is the project's primary credential and document where
    it lives (Keychain) and how to rotate it. The README's Disclaimer
    section reminds users:

    "You are responsible for the bot token and the whitelist."

    This is the opposite of the anti-pattern of treating credentials as
    casual values that might end up in commits.

    Summary: no valid credentials in the repo (zero matches for private
    keys, AWS keys, GitHub PATs, or .env-style files); the only
    token-shaped strings are Telegram's own documented placeholder, used
    inside docs/ to illustrate BotFather output; .gitignore blocks the
    common credential file patterns; production credentials live in the
    macOS Keychain and never touch the filesystem; CI secrets are
    referenced exclusively through GitHub's redacted secrets mechanism;
    GitHub secret-scanning + CodeQL + Scorecard provide active leak
    detection. The criterion is satisfied.


 Analysis 8/8

  • Static code analysis


    At least one static code analysis tool (beyond compiler warnings and "safe" language modes) MUST be applied to any proposed major production release of the software before its release, if there is at least one FLOSS tool that implements this criterion in the selected language. [static_analysis]
    A static code analysis tool examines the software code (as source code, intermediate code, or executable) without executing it with specific inputs. For purposes of this criterion, compiler warnings and "safe" language modes do not count as static code analysis tools (these typically avoid deep analysis because speed is vital). Some static analysis tools focus on detecting generic defects, others focus on finding specific kinds of defects (such as vulnerabilities), and some do a combination. Examples of such static code analysis tools include cppcheck (C, C++), clang static analyzer (C, C++), SpotBugs (Java), FindBugs (Java) (including FindSecurityBugs), PMD (Java), Brakeman (Ruby on Rails), lintr (R), goodpractice (R), Coverity Quality Analyzer, SonarQube, Codacy, and HP Enterprise Fortify Static Code Analyzer. Larger lists of tools can be found in places such as the Wikipedia list of tools for static code analysis, OWASP information on static code analysis, NIST list of source code security analyzers, and Wheeler's list of static analysis tools. If there are no FLOSS static analysis tools available for the implementation language(s) used, you may select 'N/A'.

    static_analysis — macontrol evidence

    The project applies multiple FLOSS static-analysis tools to every proposed change, well before any release:

    1. golangci-lint (FLOSS, GPL-3.0) — configured in .golangci.yml with 13 linters: errcheck, govet, ineffassign, staticcheck, unused, misspell, gocritic, revive (with exported and package-comments rules enabled), bodyclose, nolintlint, unparam, prealloc, gosec. Runs as the lint job in .github/workflows/ci.yml on every push and PR. make lint exposes the same check locally.

    2. CodeQL — .github/workflows/codeql.yml runs GitHub's semantic code analysis on every push to master.

    3. govulncheck — runs as the vuln CI job (govulncheck ./...), pinned to v1.1.4.

    4. OpenSSF Scorecard — .github/workflows/scorecards.yml runs supply-chain best-practice analysis with public results (badge in README).

    5. Codacy — third-party static analysis with public dashboard linked from README badges.

    Releases use release-please + GoReleaser; both run after CI is green, so no release tag is created without all five tools having passed.

    Summary: golangci-lint, CodeQL, govulncheck, Scorecard, and Codacy run on every push/PR. The criterion is satisfied.



    It is SUGGESTED that at least one of the static analysis tools used for the static_analysis criterion include rules or approaches to look for common vulnerabilities in the analyzed language or environment. [static_analysis_common_vulnerabilities]
    Static analysis tools that are specifically designed to look for common vulnerabilities are more likely to find them. That said, using any static tools will typically help find some problems, so we are suggesting but not requiring this for the 'passing' level badge.

    static_analysis_common_vulnerabilities — macontrol evidence

    Several of the static-analysis tools used target common vulnerabilities directly:

    • gosec (enabled in .golangci.yml) — Go security checker covering subprocess use (G204), file-permission laxity (G302/G306), insecure tempfile creation, weak crypto, integer overflow, path traversal (G304), TLS misconfig, and SQL injection patterns.
    • govulncheck — checks reachable code paths against the official Go vulnerability database (vuln.go.dev), covering both stdlib and dependencies.
    • CodeQL — semantic vulnerability detection (taint flow, injection, unsafe deserialisation, etc.) using GitHub's vulnerability query packs.
    • OpenSSF Scorecard — supply-chain vulnerability checks (vulnerable deps, pinned dependencies, signed releases, branch protection).
    • staticcheck — includes the SA category which catches correctness bugs that frequently underlie vulnerabilities (deprecated API use, unsafe type assertions, ignored errors).

    Evidence the vulnerability-focused rules fire and get acted on: commit caa55d6 ("resolve 50 golangci-lint v2 findings") explicitly itemises 5 gosec security findings fixed (file modes 0o755 → 0o750, plist 0o644 → 0o600, trusted-path file opens annotated). The criterion is satisfied.



    All medium and higher severity exploitable vulnerabilities discovered with static code analysis MUST be fixed in a timely way after they are confirmed. [static_analysis_fixed]
    A vulnerability is considered medium or higher severity if its Common Vulnerability Scoring System (CVSS) base qualitative score is medium or higher. In CVSS versions 2.0 through 3.1, this is equivalent to a CVSS score of 4.0 or higher. Projects may use the CVSS score as published in a widely-used vulnerability database (such as the National Vulnerability Database) using the most-recent version of CVSS reported in that database. Projects may instead calculate the severity themselves using the latest version of CVSS at the time of the vulnerability disclosure, if the calculation inputs are publicly revealed once the vulnerability is publicly known. Note that criterion vulnerabilities_fixed_60_days requires that all such vulnerabilities be fixed within 60 days of being made public.

    static_analysis_fixed — macontrol evidence

    No medium-or-higher severity exploitable vulnerabilities are currently outstanding from any static analyser:

    • golangci-lint with default: none and max-issues-per-linter: 0 runs clean on master (the lint job is green; CI blocks merges that introduce findings).
    • govulncheck ./... is green on master.
    • CodeQL has no open alerts on master.
    • OpenSSF Scorecard public report shows no vulnerable-dependency findings.

    Demonstrated track record of timely fixes:

    • caa55d6 "fix(ci): resolve 50 golangci-lint v2 findings" addressed all findings in a single PR with itemised release notes by linter category, including 5 gosec items (file-mode tightening, trusted-path annotations).
    • 9f045d2 "refactor: split overgrown handlers and table-drive parsers" addressed Codacy complexity findings.
    • The Music feature PR (#91) explicitly lists two refactors done to satisfy Codacy: splitting MusicCaption into per-section helpers and tickOnce into snapshot + edit-media helpers.

    CI gates merges on lint + vuln passing, so static-analysis findings cannot accumulate. The criterion is satisfied.



    It is SUGGESTED that static source code analysis occur on every commit or at least daily. [static_analysis_often]

    static_analysis_often — macontrol evidence

    Static analysis runs on every commit, not merely daily:

    .github/workflows/ci.yml triggers on push to master and on pull_request targeting master. On every such event the following jobs run in parallel:

    • lint → golangci-lint (13 linters)
    • test → go test -race + coverage floor enforcement
    • vuln → govulncheck ./...
    • fuzz-short → 30s FuzzDecode

    .github/workflows/codeql.yml runs CodeQL on every push to master.
    .github/workflows/scorecards.yml runs OpenSSF Scorecard on schedule and publishes results publicly.
    Codacy runs on every push (continuous integration with the repo).

    Concurrency is configured to cancel superseded runs (cancel-in-progress: true) so the most recent commit always has fresh results. The criterion is satisfied at the strong end (per-commit, not daily).


  • Dynamic code analysis


    It is SUGGESTED that at least one dynamic analysis tool be applied to any proposed major production release of the software before its release. [dynamic_analysis]
    A dynamic analysis tool examines the software by executing it with specific inputs. For example, the project MAY use a fuzzing tool (e.g., American Fuzzy Lop) or a web application scanner (e.g., OWASP ZAP or w3af). In some cases the OSS-Fuzz project may be willing to apply fuzz testing to your project. For purposes of this criterion the dynamic analysis tool needs to vary the inputs in some way to look for various kinds of problems or be an automated test suite with at least 80% branch coverage. The Wikipedia page on dynamic analysis and the OWASP page on fuzzing identify some dynamic analysis tools. The analysis tool(s) MAY be focused on looking for security vulnerabilities, but this is not required.

    dynamic_analysis — macontrol evidence

    Dynamic analysis is applied on every PR before release:

    1. Go native fuzzing — FuzzDecode in internal/telegram/callbacks/data_fuzz_test.go runs 30s on every PR via the fuzz-short job in .github/workflows/ci.yml. Targets the only attacker-reachable parser before the whitelist gate. Commit 0bb56cc.

    2. Race detector — go test -race -coverprofile=coverage.out ./... runs on the test matrix (ubuntu-latest + macos-14) on every push/PR. The race detector is a dynamic instrumentation tool that observes actual goroutine memory accesses at runtime.

    3. Coverage measurement — go test -coverprofile is dynamic instrumentation; the resulting profile feeds go-test-coverage which enforces a per-package floor.

    Releases are produced by release-please + GoReleaser only after CI is green, so no release ships without these dynamic checks having passed. The criterion is satisfied.



    It is SUGGESTED that if the software produced by the project includes software written using a memory-unsafe language (e.g., C or C++), then at least one dynamic tool (e.g., a fuzzer or web application scanner) be routinely used in combination with a mechanism to detect memory safety problems such as buffer overwrites. If the project does not produce software written in a memory-unsafe language, choose "not applicable" (N/A). [dynamic_analysis_unsafe]
    Examples of mechanisms to detect memory safety problems include Address Sanitizer (ASAN) (available in GCC and LLVM), Memory Sanitizer, and valgrind. Other potentially-used tools include thread sanitizer and undefined behavior sanitizer. Widespread assertions would also work.

    dynamic_analysis_unsafe — macontrol evidence

    N/A.

    macontrol is written entirely in Go, a memory-safe language with garbage collection, bounds-checked slices, no pointer arithmetic, and runtime nil-check enforcement. The build sets CGO_ENABLED=0, so no C/C++ code is linked into the binary. There is no memory-unsafe code in the project to apply this criterion to.

    (For completeness: the project does run the Go race detector via go test -race on every push/PR and a Go native fuzzer on the callback parser — but the criterion does not apply because the language is memory-safe.)



    It is SUGGESTED that the project use a configuration for at least some dynamic analysis (such as testing or fuzzing) which enables many assertions. In many cases these assertions should not be enabled in production builds. [dynamic_analysis_enable_assertions]
    This criterion does not suggest enabling assertions during production; that is entirely up to the project and its users to decide. This criterion's focus is instead to improve fault detection during dynamic analysis before deployment. Enabling assertions in production use is completely different from enabling assertions during dynamic analysis (such as testing). In some cases enabling assertions in production use is extremely unwise (especially in high-integrity components). There are many arguments against enabling assertions in production, e.g., libraries should not crash callers, their presence may cause rejection by app stores, and/or activating an assertion in production may expose private data such as private keys. Beware that in many Linux distributions NDEBUG is not defined, so C/C++ assert() will by default be enabled for production in those environments. It may be important to use a different assertion mechanism or defining NDEBUG for production in those environments.

    dynamic_analysis_enable_assertions — macontrol evidence

    The project's dynamic analysis configuration enables checks well beyond what production builds carry:

    1. Race detector enabled in tests, disabled in production
      CI: go test -race -coverprofile=coverage.out ./...
      Production build: go build -trimpath -ldflags="-s -w" CGO_ENABLED=0 — no -race
      The race detector is an extensive instrumentation layer (assertion-style runtime checks on every memory access between goroutines) that is documented to be unsuitable for production due to overhead. macontrol enables it for the test matrix on every PR and strips it from release binaries.

    2. Go native fuzzer with assertion-style checks
      FuzzDecode runs the parser against random inputs and asserts on panics, oracle violations, and structural invariants. The fuzz harness is only compiled into test binaries, never the release artifact.

    3. Test-only assertion helpers
      internal/runner/runner.go's Fake test mock asserts on the parsed (Name, Args) tuple of every subprocess call, providing test-time invariant checks the production runner does not perform.

    4. Coverage floor as a runtime assertion
      go-test-coverage runs against the live coverage profile and asserts the floor is met (total 80%, package 75%, file 50%). This is a CI-time runtime check that does not exist in production.

    These assertion-style checks are configured for test/CI runs only — production builds use stripped binaries (-s -w) with no debug info, no race detector, no fuzz harness, no coverage instrumentation. The criterion is satisfied.



    All medium and higher severity exploitable vulnerabilities discovered with dynamic code analysis MUST be fixed in a timely way after they are confirmed. [dynamic_analysis_fixed]
    If you are not running dynamic code analysis and thus have not found any vulnerabilities in this way, choose "not applicable" (N/A). A vulnerability is considered medium or higher severity if its Common Vulnerability Scoring System (CVSS) base qualitative score is medium or higher. In CVSS versions 2.0 through 3.1, this is equivalent to a CVSS score of 4.0 or higher. Projects may use the CVSS score as published in a widely-used vulnerability database (such as the National Vulnerability Database) using the most-recent version of CVSS reported in that database. Projects may instead calculate the severity themselves using the latest version of CVSS at the time of the vulnerability disclosure, if the calculation inputs are publicly revealed once the vulnerability is publicly known.

    dynamic_analysis_fixed — macontrol evidence

    No medium-or-higher severity exploitable vulnerabilities are currently outstanding from any dynamic analyser:

    • The race detector (go test -race) is green on the CI matrix (ubuntu-latest + macos-14) on master.
    • FuzzDecode runs 30s per PR on the callback parser; no panics or oracle violations have been reported in the corpus or in CI.
    • Coverage floor is met on master.

    Demonstrated track record of timely fixes for dynamic-analysis findings:

    • The cancel-stored-in-session pattern in internal/telegram/musicrefresh/refresher.go was structured specifically to avoid the race-detector and gosec false-positive interaction; the inline //nolint:gosec // cancel stored in session.cancel; called by Stop and run's defer documents why the fix is safe.
    • Commit 0bb56cc proactively added fuzz coverage to the highest-risk parser, a forward-looking dynamic-analysis investment rather than a reactive fix.
    • Commit fdbe795 ("ci: pin go install tool versions") pinned the dynamic-analysis tooling itself (govulncheck, go-test-coverage) so dynamic-check behaviour is reproducible.

    CI gates merges on the race-detector test job passing and on FuzzDecode not panicking, so dynamic-analysis findings cannot accumulate unfixed. The criterion is satisfied.



This data is available under the Community Data License Agreement – Permissive, Version 2.0 (CDLA-Permissive-2.0). This means that a Data Recipient may share the Data, with or without modifications, so long as the Data Recipient makes available the text of this agreement with the shared Data. Please credit AMiWR and the OpenSSF Best Practices badge contributors.

Project badge entry owned by: AMiWR.
Entry created on 2026-04-25 02:20:50 UTC, last updated on 2026-04-25 02:59:10 UTC. Last achieved passing badge on 2026-04-25 02:59:10 UTC.