Skip to content

Conversation

@Mossaka
Copy link
Collaborator

@Mossaka Mossaka commented Feb 12, 2026

Summary

Addresses githubnext/gh-aw-security#279 — the one-shot-token.so library contained cleartext token names in its .rodata section, discoverable via strings or objdump -s -j .rodata. This provided reconnaissance value by revealing exactly which environment variable names AWF monitors.

Changes:

  • XOR-obfuscate default token names — stored as encoded byte arrays, decoded at runtime during initialization. Prevents extraction via strings, objdump, or similar tools.
  • Hidden symbol visibility-fvisibility=hidden hides all internal symbols. Only getenv and secure_getenv are exported (via __attribute__((visibility("default")))).
  • Strip binary-s linker flag + strip --strip-unneeded removes symbol table, debug sections, and build metadata.
  • Build verificationbuild.sh now verifies no cleartext token names remain in the compiled binary.
  • encode-tokens.sh — helper script to regenerate obfuscated byte arrays when the default token list changes.

Before/After

Before: strings -a one-shot-token.so reveals:

COPILOT_GITHUB_TOKEN
GH_TOKEN
OPENAI_API_KEY
ANTHROPIC_API_KEY
...

After: strings -a one-shot-token.so shows none of the token names. nm reports "no symbols" for the full symbol table. Only getenv and secure_getenv appear in the dynamic symbol table (required for LD_PRELOAD).

Test plan

  • Local build with build.sh passes including new verification step
  • Runtime test confirms library correctly decodes obfuscated tokens and intercepts getenv
  • All 755 unit tests pass (npm test)
  • ESLint passes (0 errors, only pre-existing warnings)
  • Integration tests (CI)

🤖 Generated with Claude Code

Addresses githubnext/gh-aw-security#279 where cleartext token names
were discoverable in the .rodata section via strings/objdump.

Changes:
- XOR-obfuscate default token names so they don't appear as cleartext
  in .rodata (decoded at runtime during initialization)
- Add -fvisibility=hidden to hide all internal symbols; only getenv
  and secure_getenv are exported via visibility("default") attribute
- Add -s linker flag and strip --strip-unneeded post-build to remove
  symbol table, debug sections, and build metadata
- Add encode-tokens.sh script to regenerate obfuscated byte arrays
  when default token list changes
- Add *.so to .gitignore
- Add binutils to Dockerfile BUILD_PKGS for strip command
- Update build.sh with verification that no cleartext token names
  remain in the compiled binary

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Copilot AI review requested due to automatic review settings February 12, 2026 23:30
@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 82.88% 83.04% 📈 +0.16%
Statements 82.88% 83.03% 📈 +0.15%
Functions 82.74% 82.74% ➡️ +0.00%
Branches 74.87% 74.97% 📈 +0.10%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 84.7% → 85.3% (+0.61%) 84.2% → 84.8% (+0.60%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

@github-actions
Copy link
Contributor

Rust Build Test Results

Project Build Tests Status
fd 1/1 PASS
zoxide 1/1 PASS

Overall: PASS

All Rust projects built and tested successfully.

AI generated by Build Test Rust

@github-actions
Copy link
Contributor

Build Test: Node.js - Results

Project Install Tests Status
clsx PASS
execa PASS
p-limit PASS

Overall: PASS

All Node.js projects installed successfully and passed their test suites.

AI generated by Build Test Node.js

@github-actions
Copy link
Contributor

Deno Build Test Results

Project Tests Status
oak 1/1 ✅ PASS
std 1/1 ✅ PASS

Overall: ✅ PASS

All Deno tests completed successfully.

AI generated by Build Test Deno

@github-actions
Copy link
Contributor

Go Build Test Results

Project Download Tests Status
color 1/1 PASS
env 1/1 PASS
uuid 1/1 PASS

Overall: PASS

All Go projects successfully downloaded dependencies and passed tests.

AI generated by Build Test Go

@github-actions
Copy link
Contributor

Smoke Test Results

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP: Retrieved PR data
  • ✅ Playwright: Page title contains "GitHub"
  • ✅ File creation: /tmp/gh-aw/agent/smoke-test-copilot-21968391246.txt
  • ✅ Bash: Verified file contents

Overall Status: PASS

cc: @Mossaka

AI generated by Smoke Copilot

@github-actions
Copy link
Contributor

Bun Build Test Results

Project Install Tests Status
elysia 1/1 PASS
hono 1/1 PASS

Overall: PASS

All Bun projects built and tested successfully.

  • Bun version: 1.3.9
  • All dependencies installed without errors
  • All tests passed

AI generated by Build Test Bun

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens the one-shot-token.so LD_PRELOAD library to reduce binary reconnaissance value by preventing default token environment variable names from appearing in cleartext within the compiled ELF, while also tightening symbol visibility and adding build-time verification.

Changes:

  • XOR-obfuscates built-in default token names (decoded at runtime) and exports only getenv/secure_getenv via default visibility.
  • Strips the shared library during builds and adds a build.sh verification step to ensure token names don’t appear in strings output.
  • Adds encode-tokens.sh to regenerate the obfuscated token byte arrays and updates Docker/README accordingly.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
containers/agent/one-shot-token/one-shot-token.c Replaces cleartext default token list with runtime-decoded XOR-obfuscated bytes; exports only the interposition symbols.
containers/agent/one-shot-token/encode-tokens.sh Adds helper to generate the obfuscated byte arrays for defaults.
containers/agent/one-shot-token/build.sh Adds hardened compile flags, stripping, and binary verification against cleartext token names.
containers/agent/one-shot-token/README.md Documents the hardened build flags and obfuscation workflow.
containers/agent/Dockerfile Builds the .so with hidden visibility + stripping; installs binutils to support strip.
.gitignore Ignores .so artifacts at repo root.
Comments suppressed due to low confidence (2)

containers/agent/one-shot-token/one-shot-token.c:377

  • secure_getenv() never initializes the token list and calls get_token_index() without holding token_mutex. If a process calls secure_getenv() before getenv() (or concurrently during initialization), sensitive tokens may be missed and left in the environment, defeating the protection. Make secure_getenv follow the same locking + init_token_list() pattern as getenv (lock, init if needed, compute token_idx under the lock) before deciding whether to pass through.
__attribute__((visibility("default")))
char *secure_getenv(const char *name) {
    init_real_secure_getenv();
    init_real_getenv();

    /* If secure_getenv is not available, fall back to our intercepted getenv */
    if (real_secure_getenv == NULL) {
        return getenv(name);
    }

    int token_idx = get_token_index(name);

    /* Not a sensitive token - pass through to real secure_getenv */
    if (token_idx < 0) {
        return real_secure_getenv(name);
    }

containers/agent/one-shot-token/build.sh:32

  • build.sh now unconditionally runs strip --strip-unneeded, but strip may not be available on a developer machine; with set -e this will hard-fail. Consider checking command -v strip first and emitting a clear error instructing how to install it (e.g., binutils), or falling back to just the linker -s flag when unavailable.
# Remove remaining unneeded symbols (debug sections, build metadata)
strip --strip-unneeded "${OUTPUT_FILE}"


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +43 to +45
# Verify hardening: token names should NOT appear in binary
if strings -a "${OUTPUT_FILE}" | grep -qE '(COPILOT_GITHUB_TOKEN|OPENAI_API_KEY|ANTHROPIC_API_KEY)'; then
echo "[build] WARNING: Cleartext token names still present in binary"
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardening verification relies on strings, which may not be installed by default on all dev machines (it’s typically provided by binutils). Consider checking command -v strings and failing with a clear message (or skipping verification) rather than failing with a generic “command not found”.

This issue also appears on line 30 of the same file.

Copilot uses AI. Check for mistakes.
Comment on lines 362 to 365
__attribute__((visibility("default")))
char *secure_getenv(const char *name) {
init_real_secure_getenv();
init_real_getenv();
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are integration tests for one-shot-token behavior, but none appear to exercise the secure_getenv() interception path (a repo search under tests/ finds no secure_getenv usage). Adding an integration test that calls secure_getenv first would help prevent regressions here.

This issue also appears on line 362 of the same file.

Copilot uses AI. Check for mistakes.
* via strings(1) or objdump. Internal symbols use hidden visibility.
* Binary should be stripped after compilation (see build.sh / Dockerfile).
*
* Compile: gcc -shared -fPIC -fvisibility=hidden -o one-shot-token.so one-shot-token.c -ldl
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header compile command is now missing required link flags used by the actual build (notably -lpthread, and potentially -s/strip if you want the comment to match the hardened build). As written, following this compile line will likely fail to link due to pthread_mutex usage. Update the compile example to match build.sh / Dockerfile so the documentation is accurate.

Suggested change
* Compile: gcc -shared -fPIC -fvisibility=hidden -o one-shot-token.so one-shot-token.c -ldl
* Compile: gcc -shared -fPIC -fvisibility=hidden -o one-shot-token.so one-shot-token.c -ldl -lpthread -s

Copilot uses AI. Check for mistakes.
static const unsigned char OBF_9[] = { 0x19, 0x16, 0x1b, 0x0f, 0x1e, 0x1f, 0x05, 0x1b, 0x0a, 0x13, 0x05, 0x11, 0x1f, 0x03 }; /* length=14 */
static const unsigned char OBF_10[] = { 0x19, 0x15, 0x1e, 0x1f, 0x02, 0x05, 0x1b, 0x0a, 0x13, 0x05, 0x11, 0x1f, 0x03 }; /* length=13 */

static const struct obf_entry OBFUSCATED_DEFAULTS[11] = {
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated defaults define NUM_DEFAULT_TOKENS but the OBFUSCATED_DEFAULTS array is still sized with a hard-coded literal ("[11]"). This creates a footgun if the token list changes and someone updates the macro but forgets the array size. Prefer sizing the array with NUM_DEFAULT_TOKENS (and update encode-tokens.sh output accordingly) to keep these in sync automatically.

Suggested change
static const struct obf_entry OBFUSCATED_DEFAULTS[11] = {
static const struct obf_entry OBFUSCATED_DEFAULTS[NUM_DEFAULT_TOKENS] = {

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +49
# Verify hardening: token names should NOT appear in binary
if strings -a "${OUTPUT_FILE}" | grep -qE '(COPILOT_GITHUB_TOKEN|OPENAI_API_KEY|ANTHROPIC_API_KEY)'; then
echo "[build] WARNING: Cleartext token names still present in binary"
exit 1
else
echo "[build] Verified: no cleartext token names in binary"
fi
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The binary hardening verification only greps for three token names, but the default list includes more (e.g., GH_TOKEN, GITHUB_PAT, OPENAI_KEY, etc.). This can let regressions slip through where some default token names still appear in the compiled .so. Consider grepping for the full default token set (ideally sourced from a single list so it stays in sync with encode-tokens.sh / one-shot-token.c).

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Contributor

.NET Build Test Results

Project Restore Build Run Status
hello-world PASS
json-parse PASS

Overall: PASS

All .NET projects successfully restored, built, and ran with expected output.

AI generated by Build Test .NET

@github-actions
Copy link
Contributor

Merged PRs: fix(docker): hide credentials at direct home mount in chroot mode | fix: replace unanchored regex with string assertions in tests
Test 1 GitHub MCP merged PRs: ✅
Test 2 safeinputs-gh PR list: ✅
Test 3 Playwright title check: ✅
Test 4 Tavily search: ❌ (tool missing)
Test 5-6 file write + cat: ✅
Test 7 discussion query + comment: ✅
Test 8 build (npm ci && npm run build): ✅
Overall: FAIL

AI generated by Smoke Codex

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

🎬 THE ENDSmoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨

@github-actions
Copy link
Contributor

Java Build Test Results

Project Compile Tests Status
gson 1/1 PASS
caffeine 1/1 PASS

Overall: PASS

All Java projects built and tested successfully through the AWF firewall with Maven proxy configuration.

AI generated by Build Test Java

@github-actions
Copy link
Contributor

Chroot Version Comparison Test Results

Runtime Host Version Chroot Version Match?
Python 3.12.12 3.12.3 ❌ NO
Node.js v24.13.0 v20.20.0 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Test Status: ❌ Failed

Not all runtime versions matched between host and chroot environments. Only Go versions are identical.

AI generated by Smoke Chroot

@github-actions
Copy link
Contributor

Smoke Test Results

Last 2 Merged PRs:

  • fix(docker): hide credentials at direct home mount in chroot mode
  • Update agentic workflows for improved functionality

Test Results:

  • ✅ GitHub MCP: Retrieved merged PRs
  • ✅ Playwright: Page title verified
  • ✅ File Writing: Created test file
  • ✅ Bash Tool: Verified file contents

Overall Status: PASS

AI generated by Smoke Claude

Resolve conflicts between the C hardening approach (this PR) and the
Rust rewrite (main). Keep the hardened C implementation with XOR-obfuscated
token names, hidden symbol visibility, and stripped binary. Remove Rust
source files (Cargo.toml, src/lib.rs) that are superseded by the C approach.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

🎬 THE ENDSmoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨

@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

Chroot tests passed! Smoke Chroot - All security and functionality tests succeeded.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@github-actions
Copy link
Contributor

github-actions bot commented Feb 13, 2026

✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟

@github-actions
Copy link
Contributor

Build Test Results: Node.js ✅

All Node.js projects successfully installed and tested through the AWF firewall.

Project Install Tests Status
clsx PASS ✅ PASS
execa PASS ✅ PASS
p-limit PASS ✅ PASS

Overall: ✅ PASS

All projects installed dependencies and passed their test suites successfully.

AI generated by Build Test Node.js

@github-actions
Copy link
Contributor

C++ Build Test Results

Project CMake Build Status
fmt PASS
json PASS

Overall: PASS

All C++ projects built successfully.

AI generated by Build Test C++

@github-actions
Copy link
Contributor

Deno Build Test Results

Project Tests Status
oak 1/1 ✅ PASS
std 1/1 ✅ PASS

Overall: ✅ PASS

All Deno tests completed successfully.

AI generated by Build Test Deno

@github-actions
Copy link
Contributor

Build Test: Bun ✅

Project Install Tests Status
elysia 1/1 PASS ✅
hono 1/1 PASS ✅

Overall: PASS ✅

All Bun build tests completed successfully!

AI generated by Build Test Bun

@github-actions
Copy link
Contributor

🧪 Build Test: Go - Results

All Go projects tested successfully!

Project Download Tests Status
color 1/1 PASS ✅
env 1/1 PASS ✅
uuid 1/1 PASS ✅

Overall: PASS ✅

All Go module downloads completed successfully and all tests passed.

AI generated by Build Test Go

@github-actions
Copy link
Contributor

fix: pass ANTHROPIC_API_KEY to validation in all Claude workflows
fix(agent): enable direct api-proxy access and remove api key from env
GitHub MCP (last 2 merged PRs) ✅
Safeinputs gh pr list ✅
Playwright github.com title ✅
Tavily MCP search ❌ (tool unavailable)
File write + cat ✅
Discussion comment + build ✅
Overall status: FAIL

AI generated by Smoke Codex

@github-actions
Copy link
Contributor

Smoke Test Results

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP: Retrieved PR data successfully
  • ✅ Playwright: GitHub homepage title verified
  • ✅ File Write: Created test file at /tmp/gh-aw/agent/smoke-test-copilot-21977064054.txt
  • ✅ Bash Tool: File content verified

Overall Status: PASS 🎉

cc @Mossaka

AI generated by Smoke Copilot

@github-actions
Copy link
Contributor

Smoke Test Results

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP: Retrieved PR data
  • ✅ Playwright: Navigated to github.com (title verified: "GitHub · Change is constant. GitHub keeps you ahead. · GitHub")
  • ✅ File Write: Created /tmp/gh-aw/agent/smoke-test-claude-21977064051.txt
  • ✅ Bash: Verified file contents

Status: PASS

AI generated by Smoke Claude

@github-actions
Copy link
Contributor

✅ Java Build Test Results

All projects successfully compiled and passed tests.

Project Compile Tests Status
gson 1/1 PASS
caffeine 1/1 PASS

Overall: PASS

Both projects:

  • Downloaded dependencies through proxy successfully
  • Compiled without errors
  • All tests passed

AI generated by Build Test Java

@github-actions
Copy link
Contributor

Chroot Mode Test Results

Runtime Host Version Chroot Version Match?
Python 3.12.12 3.12.3 ❌ NO
Node.js v24.13.0 v20.20.0 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Overall Status:FAILED - Not all runtimes match between host and chroot environments.

AI generated by Smoke Chroot

@github-actions
Copy link
Contributor

Rust Build Test Results

Project Build Tests Status
fd 1/1 PASS
zoxide 1/1 PASS

Overall: PASS

All Rust projects built successfully and tests passed.

AI generated by Build Test Rust

@github-actions
Copy link
Contributor

.NET Build Test Results

Project Restore Build Run Status
hello-world PASS
json-parse PASS

Overall: PASS

All .NET projects successfully restored NuGet packages, compiled, and executed.

AI generated by Build Test .NET

@Mossaka Mossaka merged commit 4fe66bc into main Feb 13, 2026
93 checks passed
@Mossaka Mossaka deleted the security/harden-one-shot-token-binary branch February 13, 2026 06:47
Mossaka added a commit that referenced this pull request Feb 13, 2026
Accept main's C-only one-shot-token library (with XOR obfuscation from PR #776).
The Rust implementation (src/lib.rs, Cargo.toml) was deleted on main and is
no longer needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant