Skip to content

Commit c5332e6

Browse files
jolestarjolestar
andauthored
fix: restore holonbot app token path for review runs (#619)
* fix: align holonbot OIDC audience and token env precedence - add holonbot_oidc_audience input (default: holon-token-broker) to action and reusable workflow\n- request GitHub OIDC token with configured audience before broker exchange\n- restore HOLON_GITHUB_TOKEN highest-priority runtime injection in serve path\n- add regression test for HOLON_GITHUB_TOKEN precedence * fix(holonbot): default OIDC audience to holon-token-broker - default broker audience to holon-token-broker when HOLON_OIDC_AUDIENCE is unset\n- add test coverage for default audience behavior\n- update README to reflect default audience behavior --------- Co-authored-by: jolestar <host-user@example.com>
1 parent f4ddfc3 commit c5332e6

File tree

8 files changed

+71
-7
lines changed

8 files changed

+71
-7
lines changed

.github/workflows/holon-solve.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ on:
113113
required: false
114114
type: string
115115
default: 'holon-run/holon'
116+
holonbot_oidc_audience:
117+
description: 'OIDC audience used when exchanging for Holonbot app installation token'
118+
required: false
119+
type: string
120+
default: 'holon-token-broker'
116121
runs_on:
117122
description: >
118123
Runner labels as a plain string (e.g., 'ubuntu-latest' or 'self-hosted')
@@ -1190,6 +1195,7 @@ jobs:
11901195
version: ${{ inputs.version }}
11911196
build_from_source: ${{ inputs.build_from_source }}
11921197
holon_repository: ${{ inputs.holon_repository }}
1198+
holonbot_oidc_audience: ${{ inputs.holonbot_oidc_audience }}
11931199

11941200
- name: Post Summary
11951201
id: result

action.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ inputs:
3838
description: 'URL of the Holonbot Token Broker'
3939
required: false
4040
default: 'https://bot.holon.run/api/exchange-token'
41+
holonbot_oidc_audience:
42+
description: 'OIDC audience for Holonbot token broker exchange'
43+
required: false
44+
default: 'holon-token-broker'
4145
log_level:
4246
description: 'Log level for CI visibility (debug, info, progress, minimal)'
4347
required: false
@@ -104,7 +108,15 @@ runs:
104108
105109
if [ -n "$ACTIONS_ID_TOKEN_REQUEST_TOKEN" ]; then
106110
echo "Fetching OIDC token from GitHub Actions runtime..."
107-
OIDC_TOKEN=$(curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r '.value')
111+
OIDC_URL="$ACTIONS_ID_TOKEN_REQUEST_URL"
112+
if [ -n "${{ inputs.holonbot_oidc_audience }}" ]; then
113+
if [[ "$OIDC_URL" == *\?* ]]; then
114+
OIDC_URL="${OIDC_URL}&audience=${{ inputs.holonbot_oidc_audience }}"
115+
else
116+
OIDC_URL="${OIDC_URL}?audience=${{ inputs.holonbot_oidc_audience }}"
117+
fi
118+
fi
119+
OIDC_TOKEN=$(curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$OIDC_URL" | jq -r '.value')
108120
109121
if [ -n "$OIDC_TOKEN" ] && [ "$OIDC_TOKEN" != "null" ]; then
110122
if [ -z "${{ inputs.holonbot_broker_url }}" ]; then

cmd/holon/env_injection.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ func applyAnthropicAutoEnv(envVars map[string]string, fallback map[string]string
7474
}
7575

7676
func applyGitHubTokenAutoEnv(envVars map[string]string) string {
77+
if token := strings.TrimSpace(os.Getenv("HOLON_GITHUB_TOKEN")); token != "" {
78+
envVars["HOLON_GITHUB_TOKEN"] = token
79+
envVars["GITHUB_TOKEN"] = token
80+
envVars["GH_TOKEN"] = token
81+
return token
82+
}
7783
if token := strings.TrimSpace(os.Getenv("GITHUB_TOKEN")); token != "" {
7884
envVars["GITHUB_TOKEN"] = token
7985
envVars["GH_TOKEN"] = token

cmd/holon/serve_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,23 @@ func TestResolveServeRuntimeEnv_InjectsGitHubToken(t *testing.T) {
297297
}
298298
}
299299

300+
func TestResolveServeRuntimeEnv_PrefersHolonGitHubToken(t *testing.T) {
301+
t.Setenv("HOLON_GITHUB_TOKEN", "holon-token")
302+
t.Setenv("GITHUB_TOKEN", "actions-token")
303+
t.Setenv("GH_TOKEN", "")
304+
305+
got := resolveServeRuntimeEnv(context.Background())
306+
if got["HOLON_GITHUB_TOKEN"] != "holon-token" {
307+
t.Fatalf("HOLON_GITHUB_TOKEN = %q", got["HOLON_GITHUB_TOKEN"])
308+
}
309+
if got["GITHUB_TOKEN"] != "holon-token" {
310+
t.Fatalf("GITHUB_TOKEN = %q", got["GITHUB_TOKEN"])
311+
}
312+
if got["GH_TOKEN"] != "holon-token" {
313+
t.Fatalf("GH_TOKEN = %q", got["GH_TOKEN"])
314+
}
315+
}
316+
300317
func bytesToLines(raw []byte) []string {
301318
text := string(raw)
302319
if text == "" {

holonbot/README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ LOG_LEVEL=info
160160
- OIDC JWT verification enforces:
161161
- signature via GitHub JWKS
162162
- `iss` = `https://token.actions.githubusercontent.com`
163-
- `aud` in `HOLON_OIDC_AUDIENCE` (required)
163+
- `aud` in configured audiences (`HOLON_OIDC_AUDIENCE`, default: `holon-token-broker`)
164164
- standard JWT time checks (`exp`, `nbf`)
165165
- Claim validation enforces repository binding:
166166
- `repository` / `repository_owner` format and consistency
@@ -179,12 +179,10 @@ LOG_LEVEL=info
179179

180180
### Security Configuration
181181

182-
Required:
183-
184-
- `HOLON_OIDC_AUDIENCE`: Comma-separated accepted OIDC audiences.
185-
186182
Optional (defaults shown):
187183

184+
- `HOLON_OIDC_AUDIENCE=holon-token-broker` (comma-separated accepted OIDC audiences)
185+
188186
- `HOLON_REQUIRE_ACTOR_COLLABORATOR=true`
189187
- `HOLON_MIN_ACTOR_PERMISSION=read`
190188
- `HOLON_REQUIRE_JOB_WORKFLOW_REF=true`

holonbot/api/exchange-token.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { verifyOIDCToken, validateClaims } from '../lib/oidc.js';
44
const replayCache = new Map();
55
const rateLimitCache = new Map();
66
let lastCleanupAtMs = 0;
7+
const defaultOIDCAudiences = ['holon-token-broker'];
78
const permissionRank = {
89
none: 0,
910
read: 1,
@@ -50,7 +51,7 @@ function parseCSV(value) {
5051
function getRequiredAudiences(env = process.env) {
5152
const audiences = parseCSV(env.HOLON_OIDC_AUDIENCE);
5253
if (audiences.length === 0) {
53-
throw new HttpError(500, 'config.invalid', 'Missing HOLON_OIDC_AUDIENCE configuration');
54+
return defaultOIDCAudiences;
5455
}
5556
return audiences;
5657
}

holonbot/package-lock.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

holonbot/test/exchange-token.test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,27 @@ describe('exchange-token handler', () => {
113113
repository_ids: [42],
114114
permissions: { contents: 'write', pull_requests: 'write' }
115115
}));
116+
expect(verifyOIDCToken).toHaveBeenCalledWith('valid-oidc-token', { audiences: ['holon-broker'] });
117+
});
118+
119+
test('should default OIDC audience when HOLON_OIDC_AUDIENCE is not configured', async () => {
120+
delete process.env.HOLON_OIDC_AUDIENCE;
121+
verifyOIDCToken.mockResolvedValue({ sub: 'repo:owner/repo' });
122+
validateClaims.mockReturnValue({
123+
repository: 'owner/repo',
124+
owner: 'owner',
125+
repo: 'repo',
126+
repositoryId: '42',
127+
actor: 'jolestar',
128+
ref: 'refs/heads/main',
129+
runId: 'run-1',
130+
jti: 'jti-1',
131+
});
132+
133+
await handler(req, res);
134+
135+
expect(res.status).toHaveBeenCalledWith(200);
136+
expect(verifyOIDCToken).toHaveBeenCalledWith('valid-oidc-token', { audiences: ['holon-token-broker'] });
116137
});
117138

118139
test('should return 401 if auth header is missing', async () => {

0 commit comments

Comments
 (0)