Harden Device Trust with Token Permissions: Preventing Subversion with GitHub Personal Access Tokens

Device Trust is rapidly becoming a cornerstone of modern security strategies, particularly within software development lifecycles. By ensuring that code changes are initiated from trusted devices, organizations can significantly reduce the risk of supply chain attacks and unauthorized modifications. However, a critical vulnerability often overlooked lies in the potential for users to bypass these controls using Personal Access Tokens (PATs). This blog post will delve into how attackers can leverage PATs to subvert Device Trust mechanisms, and more importantly, how you can harden Device Trust with token permissions through robust management practices.

Why PATs Are a Threat to Device Trust

AspectTraditional Device Trust (Web UI)PAT‑Based Access
Authentication pointBrowser session tied to SSO and device compliance checksDirect API call with static secret
VisibilityUI logs, conditional access policiesAPI audit logs only; may be ignored
Revocation latencyImmediate when device is non‑compliantRequires token rotation or explicit revocation
Scope granularityOften coarse (read/write) per repositoryFine‑grained scopes (e.g., pull_request:writerepo:status)

A PAT can be generated with any combination of scopes that the user’s role permits. When a developer creates a token for automation, they may inadvertently grant more privileges than needed, especially if the organization does not enforce fine‑grained tokens and approvals. The result is a secret that can be used from any machine, managed or unmanaged, effectively sidestepping Device Trust enforcement.

Real‑World Consequence

Imagine an attacker who gains access to a developer’s laptop after it is stolen. They locate the file ~/.git-credentials (or a credential helper store) and extract a PAT that includes pull_request:write. Using this token they can:

  1. Pull the latest code from any repository.
  2. Approve a malicious pull request without ever opening the controlled web UI.
  3. Merge the PR, causing malicious code to flow into production pipelines.

Because the action occurs via the API, the organization’s monitoring solution sees no violation, no unmanaged device attempted to open the GitHub website. The only evidence is an audit‑log entry that a token performed the operation, which may be missed if logging and alerting are not tuned for PAT usage.

Attack Flow: Bypassing Device Trust with PATs

Let’s illustrate how an attacker might exploit this vulnerability using a GitHub example. This flow can be adapted to other platforms like GitLab, Azure DevOps, etc., but the core principles remain consistent.

Explanation:

  1. Attacker Obtains Compromised PAT: This could happen through phishing, malware, credential stuffing, or insecure storage practices by the user.
  2. GitHub API Access: The attacker uses the stolen PAT to authenticate with the GitHub API.
  3. Forge Pull Request: The attacker creates a pull request containing malicious code changes.
  4. Approve Pull Request (Bypass Device Trust): Using the API, the attacker approves the pull request without going through the standard Device Trust verification process. This is the critical bypass step.
  5. Merge Changes to Main Branch: The approved pull request is merged into the main branch, potentially introducing malicious code into production.

The “Device Trust Workflow” subgraph shows the intended secure path. Notice how the attacker completely circumvents this path by leveraging the PAT directly against the API.

Leveraging gh cli and the GitHub API with PATs

Attackers or savvy users don’t need sophisticated tools to exploit PATs. The readily available gh cli (GitHub Command Line Interface) or simple scripting using curl can be used effectively.

Approving a Pull Request with gh cli:

Assuming you have the PAT stored in an environment variable GITHUB_TOKEN:

# Export the stolen token into an environment variable (or store it in ~/.config/gh/config.yml)
export GH_TOKEN=ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXX

# Authenticate gh with the token (no interactive login required)
gh auth status  # verifies that the token is valid

# List open pull requests for a target repository
gh pr list --repo AcmeCorp/webapp --state open

# Approve and merge a specific PR (ID = 42)
gh pr review 42 --repo AcmeCorp/webapp --approve --body "Looks good to me!"
gh pr merge 42 --repo AcmeCorp/webapp --merge 

All of these actions are performed via the GitHub API behind the scenes. These simple commands bypass any Device Trust checks that would normally be required when approving a pull request through the web interface.

Approving a Pull Request with curl:

# Variables
TOKEN="ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
OWNER="AcmeCorp"
REPO="webapp"
PR_NUMBER=42

# Submit an approval review
curl -X POST \
  -H "Authorization: token $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER/reviews \
  -d '{"event":"APPROVE"}'

# Merge the pull request
curl -X PUT \
  -H "Authorization: token $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER/merge \
  -d '{"merge_method":"squash"}'

If the token includes pull_request:write permission scope, both calls succeed, and the attacker has merged malicious code without ever interacting with the controlled web flow.

Hardening Device Trust: Token Management Strategies

The key to mitigating this risk lies in proactive token management and granular permission control. Here’s a breakdown of strategies you can implement:

Disable PATs Where Possible:

This is the most secure approach, but often impractical for organizations heavily reliant on automation or legacy integrations. However, actively identify and eliminate unnecessary PAT usage. Encourage users to migrate to more secure authentication methods like GitHub Apps where feasible.

GitHub now offers Fine-Grained Personal Access Tokens (FG-PATs) which allow you to scope permissions down to specific repositories and even individual resources within those repositories. This is a significant improvement over classic PATs, but still requires careful management.

Implement Organization-Level Policies:

GitHub provides features for managing PAT usage at the organization level:

  • Require FG-PATs: Enforce the use of Fine-Grained Personal Access Tokens instead of classic PATs.
  • Restrict Token Creation: Limit who can create PATs within the organization. Consider restricting creation to specific teams or administrators.
  • Require Administrator approval: Requires an Administrators to approve the token and scope before being usable.
  • Token Expiration Policies: Set a maximum expiration time for all PATs. Shorter lifespans reduce the window of opportunity for attackers if a token is compromised.
  • IP Allowlisting (GitHub Enterprise): Restrict PAT usage to specific IP address ranges, limiting access from known and trusted networks.

GitHub introduced Fine‑grained personal access tokens (FGPATs) that let administrators define which repositories a token can access and what actions it may perform. To require FGPATs, enable the “Restrict access via personal access tokens (classic)” option in Organization Settings → Personal Access Tokens → Settings → Tokens (classic)

Focus on Repository-Level Scopes and Require Approval :

In addition to restricting the use of classic Personal Access Tokens, try to utilize Github apps and/or Oauth for access as they offer a far more robust set of configuration and controls for autonomous workloads. If still need to leverage Fine-Grain Personal access tokens, limit them to a target set of repo(s), require administrator approval, and set a maximum expiration date to limit exposure.

This provides more granular control over permissions and allows for active review/approval:

  • Restrict pull_request:write Permission: The pull_request:write permission is particularly dangerous as it allows users to approve pull requests without Device Trust verification. Consider removing this permission from PATs unless absolutely necessary.
  • Least Privilege Principle: Grant only the minimum permissions required for each PAT. Avoid broad “repo” scope access whenever possible. FG-PATs make this much easier.
  • Code Owners Review: Enforce code owner reviews on all pull requests, even those approved via API. This adds an extra layer of security and helps detect malicious changes.

Token Auditing and Monitoring:

  • Regularly Review PAT Usage: Identify unused or overly permissive tokens.
  • Monitor API Activity: Look for suspicious activity, such as unexpected pull request approvals or changes made outside of normal working hours. GitHub provides audit logs that can be integrated with SIEM systems.
  • Automated Scanning: Use tools to scan code repositories and identify hardcoded PATs.

User Education:

Educate developers about the risks associated with PATs and best practices for secure token management, including:

  • Never commit PATs to source control.
  • Use strong passwords and multi-factor authentication.
  • Rotate tokens regularly.
  • Report any suspected compromise immediately.

Conclusion

Device Trust is a vital security component, but it’s not a silver bullet. Attackers will always seek the path of least resistance, and PATs represent a significant vulnerability if left unmanaged. By implementing robust token management strategies – including disabling unnecessary PATs, enforcing granular permissions, and actively monitoring API activity – you can harden Device Trust with token permissions and significantly reduce your risk of supply chain attacks. Remember that security is a layered approach; combining Device Trust with strong token controls provides the most comprehensive protection for your software development lifecycle.

Detecting Device Trust Certificate Exports: How to Build Custom SIEM/XDR Rules

Introduction

In modern Zero‑Trust environments a Device Trust certificate is the linchpin that provides endpoints the ability to authenticate independently and extends an additional layer of actionable controls. These certificates are typically combined into PKCS #12 (*.p12) bundles and imported into the Windows Certificate Store or macOS Keychain with the non‑exportable private key attribute set. The intention is clear: users should never be able to pull the private key out of the local store.

In practice, however, attackers (or careless insiders) can still attempt to export the private key using native utilities such as certutil.exe on Windows or the security CLI on macOS. Because the export operation leaves an audit trail in system logs, detecting Device Trust certificate exports becomes a realistic and valuable detection use‑case for any SIEM or XDR platform.

This blog walks you through:

  • Why non‑exportable flags are not always enough
  • The exact commands used to attempt to export on Windows and macOS
  • The log events generated by each those commands
  • How to translate that knowledge into reusable Sigma detection rules

Device Trust Certificates – The Security Goal and the Reality

A Device Trust certificate forums an endpoint’s identity for an additional layer of authentication and authorization in your environment . When a service receives a TLS client‑certificate handshake it can verify that the device its identity, managed, and security posture.

Administrators usually elect to set the NoExport option when importing with certutil in Windows and/or rely on the -x (non-extractable) option when using security import in macOS. Setting these flags tell the OS to keep the private key inside a protected store and not to write them to disk in clear text.

Why it still matters:

  • The protection is enforced at the API level, not at the file‑system level.
  • A user with administrative rights can invoke privileged utilities that request the private key from the CryptoAPI (Windows) or Security.framework (macOS).
  • Attackers who have already compromised an account often have the same ability to run those utilities or advanced forensics tools

Note: If certificates are not set as non-exportable when loaded into the users certificate store. The user will be able to extract the private key and full key bundle without any additional permissions.

Therefore, detection must focus on the act of attempting an export, not just the presence of a non‑exportable flag.

Threat Scenario – Exporting a Private Key for Lateral Movement

Consider an attacker who has gained local admin rights on a laptop that is enrolled in Device Trust. The attacker’s objectives may include:

  1. Steal the private key to impersonate the device when communicating with internal services.
  2. Reuse the certificate on another machine to bypass device compliance checks.
  3. Exfiltrate the key for later use in a authentication attacks or user impersonation .

Even if the attacker cannot directly read the private key from the store, they attempt to call certutil -exportpfx (Windows) or security export (macOS) to generate a new PKCS #12 bundle. The resulting file can be copied to a USB drive, uploaded to cloud storage, or transmitted over an encrypted tunnel—each step leaving observable footprints.

Export Techniques on Windows

Using certutil.exe

The most common native tool is certutil.exe. Users can attempt export from their local store like:

certutil -exportpfx -p "" MY <Thumbprint> C:\Temp\device-trust.p12
  • -p "" supplies an empty password (or a user‑chosen one).
  • My is the personal store where Device Trust certificates live.
  • <Thumbprint> identifies the exact certificate to export.

If the key was not marked non‑exportable or it was specifically marked as exportable, certutil will still succeed when run under a privileged context because it uses the Cryptographic Service Provider (CSP) API that can request the private key material for an authorized user.

PowerShell Alternative

PowerShell’s Export-PfxCertificate cmdlet also works:

$cert = Get-ChildItem Cert:\CurrentUser\My\<Thumbprint>
Export-PfxCertificate -Cert $cert -FilePath C:\Temp\device-trust.p12 -Password (ConvertTo-SecureString -String "" -AsPlainText -Force)

Both commands generate a *.p12 file that can be moved off the host.

What Gets Logged?

  • Windows Security Auditing – Event ID 4688 (A new process has been created) logs the full command line when audit policy Process Creation is enabled.
  • Sysmon (if installed) – Event ID 1 captures the same data with additional hashes for the executable.
  • Application Logscertutil may emit an informational entry in the System or Application log, but process creation events are the most reliable source.

Note: The windows certificateservicesclient lifecycle system now also natively creates an event with id 1007 when any certificate is exported from the local certificate store (including Public-only and CA Chain certificates). This means it can commonly be triggered by various applications and can’t be scoped to specific certificates, but there is already a community approved Sigma rule for the event.

Export Techniques on macOS

Using the security CLI

On macOS the security command can read from the login keychain and write a PKCS #12 bundle:

security export -k ~/Library/Keychains/login.keychain-db -t priv -p "" -o /tmp/device-trust.p12
  • -k points to the keychain file.
  • -t priv requests private keys only.
  • -p "" supplies an empty password for the output file (or a user‑chosen one).

If the certificate’s private key is stored in the Secure Enclave, the command may prompt for Touch ID or the user’s password. However, an attacker with a compromised admin session can bypass that prompt by using sudo.

Note: In newer versions of MacOSX the native tools do still respect the non-extractable attribute set when the certificate is imported, even with elevated privileges. But given its just an encrypted db, you can still utilize thrid-party tools like chainbreaker to export the keys with a password and hexdump.

What Gets Logged?

  • Unified Logging (os_log) – The subsystem com.apple.security.keychain logs messages such as “Exported private key”.
  • Auditd – If audit is enabled (audit -s), an execve record for /usr/bin/security with the export arguments appears.
  • Console.app – Shows the same entries, but for automated detection we rely on the log files under /var/log/system.log or the structured logging API.

Note: It may also be possible, even with non-exportable option set, to utilize Mimikatz like mimikatz log "crypto::certificates /export /systemstore:my" exit or Chainbreaker like python -m chainbreaker --export-x509-certificates to extract certificates from the user-space during post exploitable; but that feels like a whole different topic entirely.

Building Detection Rules in Sigma

Sigma is a vendor‑agnostic rule format that can be translated into SPL (Splunk), KQL (Sentinel), Lucene DSL (Elastic) and many others. Below are two examples—one for Windows, one for macOS—targeting the export commands discussed earlier.

Windows Sigma Rule – Detect CertUtil Export

title: Detection of Device Trust Certificate Export via certutil
id: e7c9b1a4-3d6f-4eaa-b5c8-0c2f6a9c1234
status: stable
description: |
  Detects execution of certutil.exe with the -exportpfx flag which is commonly used to extract a Device Trust private key even when it is marked non‑exportable.
author: Michael Contino
date: 2025-11-06
logsource:
  product: windows
  service: sysmon
detection:
  selection_process:
    Image|endswith: '\\certutil.exe'
  selection_export:
    CommandLine|contains: '-exportpfx'
  condition: all of selection_*
fields:
  - CommandLine
  - ParentImage
  - User
level: high
tags:
  - attack.t1552.006   # Unsecured Credentials: Private Keys
  - detection.DeviceTrustExport

If you prefer the native Windows Security log, change service to security and use Event ID 4688 in the detection block.

macOS Sigma Rule – Detect security Export

title: Detection of Device Trust Certificate Export via security CLI
id: a1f4c9e2-7b2a-44d5-a6f3-58c9f2d8b765
status: stable
description: |
  Flags execution of the macOS `security` command with arguments that request export of private keys, indicating an attempt to extract a Device Trust certificate.
author: Michael Contino
date: 2025-11-06
logsource:
  product: macos
  service: auditd
detection:
  selection_process:
    exe|endswith: '/usr/bin/security'
  selection_export:
    cmdline|contains: 'export'
  selection_privkey:
    cmdline|contains: '-t priv'
  condition: all of selection_*
fields:
  - exe
  - cmdline
  - auid
level: high
tags:
  - attack.t1552.006   # Unsecured Credentials: Private Keys
  - detection.DeviceTrustExport

For environments that rely on Unified Logging, replace service with osquery or a custom parser that extracts the com.apple.security.keychain messages.

Converting Sigma to Platform Queries

Most SIEMs provide an online converter (e.g., Sigma Converter at sigmahq.io). Below are quick examples for three popular platforms.

Splunk SPL

index=windows sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational"
(Image="*\\certutil.exe" AND CommandLine="* -exportpfx*")

Elastic Lucene DSL

{
  "query": {
    "bool": {
      "must": [
        { "wildcard": { "process.executable": "*certutil.exe" }},
        { "wildcard": { "process.command_line": "*-exportpfx*" }}
      ]
    }
  }
}

Azure Sentinel KQL

Sysmon
| where Image endswith @"\certutil.exe"
| where CommandLine contains "-exportpfx"

Apply the same conversion logic to the macOS rule, swapping process_name for exe and adjusting the field names accordingly.

Attack Flow Diagram – Exporting a Device Trust Certificate

Visualizing the steps helps analysts understand the context of an alert.

graph TD
    A[Compromised Endpoint] --> B[Locate Device Trust Cert in Store]
    B --> C{Export Attempt}
    C -->|Windows| D[certutil -exportpfx]
    C -->|macOS| E[security export -t priv]
    D --> F[PKCS12 file written to TEMP file]
    E --> F
    F --> G[Copy to Staging Location USB, Share, Cloud]
    G --> H[Exfiltration over Network or Physical Media]
    H --> I[Attacker Reuses Private Key on Remote Service]
    style A fill:#ffcccc,stroke:#c00
    style I fill:#ccffcc,stroke:#090

Explanation of the flow

  1. Compromised Endpoint – The attacker already has local admin or system privileges.
  2. Locate Device Trust Cert – Queries the certificate store (certutil -store My or security find‑identity).
  3. Export Attempt – Executes a native export tool (Windows or macOS).
  4. PKCS12 file written – The private key is now in clear text inside the .p12.
  5. Copy to Staging Location – Moves the file to a place where it can be exfiltrated.
  6. Exfiltration – Could be a cloud upload, SMB share copy, or USB drop.
  7. Attacker Reuses Private Key – The stolen key is used for impersonation, lateral movement, or credential stuffing against services that trust the Device Trust certificate.

Deploying and Tuning Your Rules

Enriching Alerts

When an export is detected, enrich the event with:

  • Certificate Thumbprint – Extracted from the command line to correlate with asset inventory.
  • Process Hash – Compare against known good binaries (e.g., Microsoft‑signed certutil.exe).
  • Endpoint Context – OS version, posture level, logged in user(s), and serial number/UUIDs.

Enrichment enables faster triage: if the exporter is a legitimate admin running from a hardened workstation, you may downgrade the alert. Otherwise, trigger an automated response.

Preventive Controls

Detection is only half of the story. Harden the environment to reduce the chance that an attacker can run the export commands:

ControlWindowsmacOS
Set Non-Exportable OptionsEnsure you still utilize export blocking options when importing certutil -importPFX [PFXfile] NoExportLikewise specific the key as non-extractable with the -x when importing security import <p12_path> -x
AppLocker / SRPBlock certutil.exe except for signed admin scriptsUse /usr/sbin/launchd policies to restrict security binary execution
Group PolicySet Do not allow private key export in PKI templates (doesn’t fully prevent certutil)Enable Secure Enclave only keys (-T -s) which refuse export without Touch ID
Audit PoliciesEnable “Process Creation” and “Credential Access” sub‑categoriesTurn on auditd with execve monitoring for /usr/bin/security

Why Detection Complements Non‑Exportable Keys

  • Non‑exportable flags protect against casual dumping but do not stop a privileged user from invoking OS APIs that return the private key material.
  • Native utilities (certutil.exe, security) are widely available, leaving an audit trail that can be detected by any modern SIEM/XDR platform.
  • By crafting Sigma rules focused on command‑line arguments and process creation events, you gain a vendor‑agnostic detection layer that works across Windows and macOS fleets.

Implementing the examples in this post will give your organization an early warning system for detecting Device Trust certificate exports, reducing the risk of credential theft, lateral movement, and unauthorized device impersonation.

Taking Action

  1. Deploy the SIEM/XDR rules above into your rule repository.
  2. Enable detailed process creation auditing on all Windows endpoints (Event ID 4688) and auditd logging on macOS.
  3. Test the detection by intentionally exporting a test certificate in a lab environment – verify that alerts fire with the expected context.
  4. Harden your endpoint policies to restrict certutil and security usage to approved admin accounts only.

Stay ahead of attackers and resistant users alike, but understand the “non‑exportable” flag is not always enough. Detecting Device Trust certificate exports gives you the visibility you need to protect the cryptographic foundation of your Zero‑Trust architecture.

MAX98357A for Radza Zero 3 – Giving Skelly a Voice with I2S and GPIO

When I started my AI Powered Skelly project built with a Radxa Zero 3 I quickly discover that the board does not ship with on‑board audio. This was a problem, because I really wanted to utilize the existing speaker within the 3ft Dancing Skeleton animatronic as a more natural source of audio output. To solve this I choose to use a MAX98357A mono amplifier to drive audio output to small speaker. This article walks you through everything you need to get the MAX98357A working on a Radxa Zero 3, from wiring the pins to enabling I2S software and finally creating the custom device‑tree overlay that makes the kernel aware of the hardware.

 MAX98357A

Why Choose MAX98357A for Radxa Zero 3?

FeatureMAX98357ATypical Alternatives
Power consumption< 100 mW (idle)> 200 mW for many DACs
Output typeMono, 3 W Class‑DStereo, often larger footprint
InterfaceI2S digital audio input onlyPCM/I2C/USB combos
Gain controlFixed (9 dB default) or external pinVariable via software registers
Shutdown pinOptional hardware lineOften not present

Because the amplifier needs only three I2S signals (BCLK, LRCK and DATA) plus power and ground, it can be wired directly to the standard I2S pins on the Radxa Zero 3 without any extra level shifters. The board’s 5 V rail supplies the amplifier’s VIN, while the GND pins provide a clean reference.

GPIO Pinout – Mapping MAX98357A to Radxa Zero 3

The Radxa Zero 3 exposes its I2S‑3 controller (named I2S3_M0) on the following header pins:

MAX98357A pinFunctionRadxa Zero 3 Pin #SoC GPIO name
VIN5 V power2 or 4 5v VDD_5V
GNDGround6, 9, 14, 20, 25, 30, 34, 39GND
BCLKI2S Bit Clock12I2S3_SCLK_M0
DIN (DATA)I2S Serial Data In40I2S3_SDO_M0
LRC (LRCK)I2S Left‑Right Clock35I2S3_LRCK_M0
GAIN (optional)Fixed gain selector – connect to GND for 6 dB, leave floating for default 9 dBany free GND (e.g., 20)GND
SD (Shutdown)Active‑low shutdown – pull low to muteany free GPIO (e.g., 16 with optional PWM)GPIO3_B1/PWM8_M0

Tip: The GAIN and SD pins are optional. For a simple “always on” configuration you can leave them floating. If you need hardware mute, connect the SD pin to a spare GPIO and drive it low when you want silence.

Wiring Diagram

Wiring MAX98357A for Radxa Zero 3
Radxa Zero 3                 MAX98357A
---------------------------  -----------------
Pin 4   (+5 V)               VIN  (Red)
Pins 6/9/... (GND)           GND  (Black)
Pin 12  I2S3_SCLK_M0 (BCLK)  BCLK (Blue)
Pin 40  I2S3_SDO_M0  (DATA)  DIN  (Orange)
Pin 35  I2S3_LRCK_M0 (LRCK)  LRC  (Green)
Pin 16  (optional)           GAIN (Purple Doted)
Pin 18 (optional)            SD   (Black Doted)

All connections are 5 V tolerant on the Radxa Zero 3, and the MAX98357A operates comfortably from 2.7 V up to 5.5 V, so no level shifting is required.

Preparing the Software – Enabling I2S in the Kernel

The Radxa Zero 3 runs a Debian‑based OS (or Ubuntu) with a Linux kernel that already contains an I2S controller driver (i2s-axg). However, the default device tree does not expose the I2S3_M0 peripheral for audio output. To make it usable you must:

  1. Install the overlay tooling – Radxa provides rsetup and a set of scripts to compile and install custom overlays.
   sudo apt-get update
   sudo apt-get install rsetup device-tree-compiler
  1. Disable any conflicting overlay – The board ships with an i2s3-m0 overlay that is intended for microphone input. You need to remove or disable it before loading your speaker overlay.
   sudo rsetup overlay disable i2s3-m0
  1. Compile the custom overlay (shown in the next section) and install it:
   sudo rsetup overlay add radxa-zero3-max98357a.dts
  1. Reboot to let the kernel load the new device‑tree node.
   sudo reboot
  1. Verify that the I2S PCM device appears under /dev.
   aplay -l
   # You should see something like:
   # card 0: Rockchip [rockchip i2s], device 0: I2S3 PCM [...]
  1. Test audio playback – Use aplay with a raw PCM file or any WAV that matches the default format (16‑bit, 44.1 kHz, mono).
   aplay -D plughw:0,0 /usr/share/sounds/alsa/Front_Center.wav

If you hear sound from your speaker, the hardware and software stack are correctly configured.

The Custom Device‑Tree Overlay – radxa-zero3-max98357a.dts

Below is the complete overlay source that tells the kernel to expose I2S3_M0 as a PCM output and optionally controls the GAIN and SD pins as GPIOs.

/dts-v1/;
/plugin/;

#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/pinctrl/rockchip.h>

/ {
    metadata {
        title = "Enable MAX97357A on I2S3-M0";
        compatible = "radxa,zero3";
        category = "audio";
        description = "Enable MAX97357A on I2S3-M0";
        exclusive = "GPIO3_A0", "GPIO3_A3", "GPIO3_A4", "GPIO3_A5", "GPIO3_A6", "i2s3_2ch";
    };
};

&{/} {
    max98357a_codec: max98357a {
        #sound-dai-cells = <0>;
        compatible = "maxim,max98357a";
        sdmode-gpios = <&gpio3 RK_PA0 GPIO_ACTIVE_HIGH>;
        sdmode-delay = <5>;
        status = "okay";
    };

    sound_ext_card: sound-ext-card {
        #address-cells = <1>;
        #size-cells = <0>;
        status = "okay";
        compatible = "simple-audio-card";
        simple-audio-card,format = "i2s";
        simple-audio-card,mclk-fs = <256>;
        simple-audio-card,name = "snd_max98357a_dac";
        simple-audio-card,dai-link@0 {
            reg = <0>;
            format = "i2s";
            cpu {
                sound-dai = <&i2s3_2ch>;
            };
            codec {
                sound-dai = <&max98357a_codec>;
            };
        };
    };
};

&i2s3_2ch {
    pinctrl-0 = <&i2s3m0_lrck &i2s3m0_sclk &i2s3m0_sdi &i2s3m0_sdo>;
    status = "okay";
};

How the Overlay Is Applied

  1. Compilersetup overlay add radxa-zero3-max98357a.dts runs dtc -@ -I dts -O dtb internally and places the resulting .dtbo file under /boot/overlays.
  2. Activate – The same command updates /boot/extlinux/extlinux.conf to load the overlay at boot.
  3. Boot – During kernel initialization, the overlay patches the base device tree, making the simple-audio-card node visible to ALSA.

Controlling Shutdown from Userspace

Because we exposed the optional pins as standard Linux GPIOs, you can manipulate them with the sysfs interface or via the newer libgpiod library.

Using sysfs (quick test)

# Export GPIO11 (shutdown) and keep high (amp on)
echo 16 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio16/direction
echo 1 > /sys/class/gpio/gpio16/value  # 1 = not shutdow

Using libgpiod (modern approach)

#include <gpiod.h>

int main(void) {
    struct gpiod_chip *chip;
    struct gpiod_line *sd;

    chip = gpiod_chip_open_by_name("gpiochip3");
    sd   = gpiod_chip_get_line(chip, 1);    // GPIO16

    gpiod_line_request_output(sd,   "max98357a_sd",   1);

    /* ... later you can toggle them */
    gpiod_line_set_value(sd,   0); // shutdown amp
}

Container‑Friendly Deployment

If you are running audio workloads inside Docker on the Radxa Zero 3, keep the following in mind:

RequirementHow to satisfy
Access to /dev/sndAdd --device /dev/snd:/dev/snd (Docker) or mount the device in the pod spec.
GPIO controlMount /sys/class/gpio read‑write or use the gpiod socket (/run/gpiod).
Real‑time scheduling (optional)Use --cap-add SYS_NICE and set ulimit -r 99.
Overlay persistenceThe overlay is applied at boot, so containers do not need to modify it. Just ensure the host kernel has loaded the PCM device before container start.

A minimal Dockerfile for an audio player might look like:

FROM debian:bookworm-slim

RUN apt-get update && \
    apt-get install -y alsa-utils libgpiod2 && \
    rm -rf /var/lib/apt/lists/*

COPY myplayer.sh /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/myplayer.sh"]

Run it with:

docker run --rm -it \
  --device /dev/snd:/dev/snd \
  -v /sys/class/gpio:/sys/class/gpio:rw \
  my-audio-player

Now your container can play back WAV files through the MAX98357A and mute/unmute using GPIO.

Troubleshooting Checklist

SymptomLikely CauseFix
No sound, aplay returns “Invalid argument”I2S node not enabledVerify overlay loaded (dmesg | grep i2s3) and run aplay -l.
Crackling or clicksClock mismatch or bad power supplyEnsure VIN is stable 5 V, add a decoupling capacitor (10 µF) near the amplifier.
Speaker only plays one channelLRC pin mis‑wired or wrong pinmuxDouble‑check that Pin 35 is connected to LRC and i2s3_m0_pins includes it.
Amplifier stays silent even after GPIO highSD pin tied lowRemove the shutdown connection or set GPIO16 value to 1.
Gain not changingGAIN pin left floating while expecting -6 dBConnect GAIN to a GPIO and drive it low, or permanently tie to GND for fixed gain.

Use journalctl -k to see kernel messages about the audio card; any “probe failed” lines usually indicate a mis‑matched overlay.

Security Considerations

Even though this guide focuses on hardware integration, running audio services on an edge device still raises security questions:

  1. GPIO Exposure – Unrestricted access to /sys/class/gpio allows any local user (or compromised container) to toggle the amplifier’s pins, potentially causing denial‑of‑service or odd behavior. Restrict GPIO permissions using Linux capabilities or mount namespaces.
  2. Audio Injection – If your device accepts network streams, validate source authenticity (TLS, signed manifests) before feeding data to ALSA.
  3. Kernel Attack Surface – Custom overlays modify the kernel’s view of hardware. Ensure only trusted administrators can write overlay files; use immutable boot partitions where possible.

Conclusion

The MAX98357A for Radxa Zero 3 offers a lightweight, low‑cost way to add mono audio output to any project that runs on this SBC. By wiring three I2S pins and enabling the I2S controller through a custom device‑tree overlay I was able to give the AI Skelly the power to speak.

Up next we look at how to programmatic trigger an event, in order to capture an image from a simple attached webcam. Then how to process the image via a standard openAI style API call to a local AI service like ollama serve or LMStuido server. In order to ask targeted questions about the capture image, such as the Halloween costumes in frame. The we can use the prompt response to generate a unique voice line response with services like piper. All while keeping everything completely offline for privacy and security.