GITHUB_TOKEN permissions default to more than you need

The default token has write access to most of the repository, and declaring a permissions block switches the job to exactly what is listed.

permissions:
  contents: read

jobs:
  deploy:
    permissions:
      contents: read
      id-token: write        # for OIDC
      packages: write        # to push an image
    steps: [...]

Declaring it at the workflow level sets a floor and each job can narrow further, which is the arrangement that survives somebody adding a job. The id-token: write permission is the one people meet by error message rather than by reading, because without it the OIDC token request fails with a message about a missing token rather than a missing permission.