Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ coverage/
.nyc_output/
*.swp
*.swo
*.so
*~
.idea/

Expand Down
33 changes: 15 additions & 18 deletions containers/agent/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,6 @@
# NOTE: ARG declared before first FROM is global and available in all FROM statements
ARG BASE_IMAGE=ubuntu:22.04

# Multi-stage build: Use official Rust image to build one-shot-token library
# SECURITY: Using official rust:1.77-slim image prevents executing unverified
# scripts from the internet during build time (supply chain attack mitigation)
# NOTE: Rust 1.77+ required for C string literal syntax (c"...") used in src/lib.rs
FROM rust:1.77-slim AS rust-builder

# Copy one-shot-token source files
COPY one-shot-token/Cargo.toml /tmp/one-shot-token/Cargo.toml
COPY one-shot-token/src/ /tmp/one-shot-token/src/

# Build the one-shot-token library
WORKDIR /tmp/one-shot-token
RUN cargo build --release

# Main stage
FROM ${BASE_IMAGE}

# Install required packages and Node.js 22
Expand Down Expand Up @@ -85,9 +70,21 @@ RUN chmod +x /usr/local/bin/setup-iptables.sh /usr/local/bin/entrypoint.sh /usr/

# Copy pre-built one-shot-token library from rust-builder stage
# This prevents tokens from being read multiple times (e.g., by malicious code)
# SECURITY: Using multi-stage build with official Rust image avoids executing
# unverified scripts from the internet during build time
COPY --from=rust-builder /tmp/one-shot-token/target/release/libone_shot_token.so /usr/local/lib/one-shot-token.so
# Build flags: -fvisibility=hidden hides internal symbols, -s strips at link time
COPY one-shot-token/one-shot-token.c /tmp/one-shot-token.c
RUN set -eux; \
BUILD_PKGS="gcc libc6-dev binutils"; \
apt-get update && \
( apt-get install -y --no-install-recommends $BUILD_PKGS || \
(rm -rf /var/lib/apt/lists/* && apt-get update && \
apt-get install -y --no-install-recommends $BUILD_PKGS) ) && \
gcc -shared -fPIC -fvisibility=hidden -O2 -Wall -s \
-o /usr/local/lib/one-shot-token.so /tmp/one-shot-token.c -ldl -lpthread && \
strip --strip-unneeded /usr/local/lib/one-shot-token.so && \
rm /tmp/one-shot-token.c && \
apt-get remove -y $BUILD_PKGS && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*

# Install Docker stub script that shows helpful error message
# Docker-in-Docker support was removed in v0.9.1
Expand Down
19 changes: 0 additions & 19 deletions containers/agent/one-shot-token/Cargo.toml

This file was deleted.

33 changes: 27 additions & 6 deletions containers/agent/one-shot-token/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,14 @@ In chroot mode, the library must be accessible from within the chroot (host file

### In Docker (automatic)

The Dockerfile compiles the Rust library during image build:
The Dockerfile compiles the library during image build with hardened flags:

```dockerfile
RUN cargo build --release && \
cp target/release/libone_shot_token.so /usr/local/lib/one-shot-token.so
RUN gcc -shared -fPIC -fvisibility=hidden -O2 -Wall -s \
-o /usr/local/lib/one-shot-token.so \
/tmp/one-shot-token.c \
-ldl -lpthread && \
strip --strip-unneeded /usr/local/lib/one-shot-token.so
```

### Locally (for testing)
Expand All @@ -175,6 +178,24 @@ Requires Rust toolchain (install via [rustup](https://rustup.rs/)):

This builds `target/release/libone_shot_token.so` and creates a symlink `one-shot-token.so` for backwards compatibility.

### Binary Hardening

The build applies several hardening measures to reduce reconnaissance value:

- **XOR-obfuscated token names**: Default token names are stored as XOR-encoded byte arrays
and decoded at runtime. This prevents extraction via `strings` or `objdump -s -j .rodata`.
- **Hidden symbol visibility**: `-fvisibility=hidden` hides all internal symbols by default.
Only `getenv` and `secure_getenv` are exported (required for LD_PRELOAD interposition).
- **Stripped binary**: `-s` flag and `strip --strip-unneeded` remove the symbol table,
debug sections, and build metadata.

To regenerate the obfuscated byte arrays after changing default token names:

```bash
./encode-tokens.sh
# Paste the output into one-shot-token.c, replacing the OBFUSCATED_DEFAULTS section
```

## Testing

### Basic Test (Default Tokens)
Expand Down Expand Up @@ -305,7 +326,7 @@ This library is one layer in AWF's security model:

## Files

- `src/lib.rs` - Library source code (Rust)
- `Cargo.toml` - Rust package configuration
- `build.sh` - Local build script
- `one-shot-token.c` - Library source code (token names are XOR-obfuscated)
- `build.sh` - Local build script (includes hardening flags and verification)
- `encode-tokens.sh` - Generates XOR-encoded byte arrays for default token names
- `README.md` - This documentation
49 changes: 29 additions & 20 deletions containers/agent/one-shot-token/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,27 @@ LINK_FILE="${SCRIPT_DIR}/one-shot-token.so"

echo "[build] Building one-shot-token with Cargo..."

cd "${SCRIPT_DIR}"

# Build the release version
cargo build --release

# Determine the output file based on platform
if [[ "$(uname)" == "Darwin" ]]; then
OUTPUT_FILE="${SCRIPT_DIR}/target/release/libone_shot_token.dylib"
echo "[build] Successfully built: ${OUTPUT_FILE} (macOS)"
else
OUTPUT_FILE="${SCRIPT_DIR}/target/release/libone_shot_token.so"
echo "[build] Successfully built: ${OUTPUT_FILE}"

# Create symlink for backwards compatibility (Linux only)
if [[ -L "${LINK_FILE}" ]]; then
rm "${LINK_FILE}"
fi
ln -sf "target/release/libone_shot_token.so" "${LINK_FILE}"
echo "[build] Created symlink: ${LINK_FILE} -> target/release/libone_shot_token.so"
fi
# Compile as a shared library with hardened build flags:
# -shared: create a shared library
# -fPIC: position-independent code (required for shared libs)
# -fvisibility=hidden: hide all symbols by default (only getenv/secure_getenv
# are exported via __attribute__((visibility("default"))))
# -ldl: link with libdl for dlsym
# -lpthread: link with pthread for mutex
# -O2: optimize for performance
# -Wall -Wextra: enable warnings
# -s: strip symbol table and relocation info at link time
gcc -shared -fPIC \
-fvisibility=hidden \
-O2 -Wall -Wextra -s \
-o "${OUTPUT_FILE}" \
"${SOURCE_FILE}" \
-ldl -lpthread

# Remove remaining unneeded symbols (debug sections, build metadata)
strip --strip-unneeded "${OUTPUT_FILE}"

echo "[build] Successfully built: ${OUTPUT_FILE}"

# Verify it's a valid shared library
if file "${OUTPUT_FILE}" | grep -qE "shared object|dynamically linked"; then
Expand All @@ -37,3 +38,11 @@ else
echo "[build] ERROR: Output is not a valid shared library"
exit 1
fi

# 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"
Comment on lines +42 to +44
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.
exit 1
else
echo "[build] Verified: no cleartext token names in binary"
fi
Comment on lines +42 to +48
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.
54 changes: 54 additions & 0 deletions containers/agent/one-shot-token/encode-tokens.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/bin/bash
# Generate XOR-obfuscated byte arrays for default token names.
# Run this script whenever the default token list changes, then paste
# the output into one-shot-token.c (replacing the OBFUSCATED_DEFAULTS section).
#
# The obfuscation prevents token names from appearing as cleartext strings
# in the .rodata section of the compiled binary. This is NOT cryptographic
# security -- a determined attacker can reverse the XOR. The goal is to
# defeat casual reconnaissance via strings(1) / objdump.

set -euo pipefail

KEY=0x5A

TOKENS=(
"COPILOT_GITHUB_TOKEN"
"GITHUB_TOKEN"
"GH_TOKEN"
"GITHUB_API_TOKEN"
"GITHUB_PAT"
"GH_ACCESS_TOKEN"
"OPENAI_API_KEY"
"OPENAI_KEY"
"ANTHROPIC_API_KEY"
"CLAUDE_API_KEY"
"CODEX_API_KEY"
)

echo "/* --- BEGIN GENERATED OBFUSCATED DEFAULTS (key=0x$(printf '%02X' $KEY)) --- */"
echo "/* Re-generate with: containers/agent/one-shot-token/encode-tokens.sh */"
echo "#define NUM_DEFAULT_TOKENS ${#TOKENS[@]}"
echo ""

for i in "${!TOKENS[@]}"; do
token="${TOKENS[$i]}"
printf "static const unsigned char OBF_%d[] = { " "$i"
for ((j=0; j<${#token}; j++)); do
byte=$(printf '%d' "'${token:$j:1}")
encoded=$((byte ^ KEY))
if ((j > 0)); then
printf ", "
fi
printf "0x%02x" "$encoded"
done
printf " }; /* length=%d */\n" "${#token}"
done

echo ""
echo "static const struct obf_entry OBFUSCATED_DEFAULTS[${#TOKENS[@]}] = {"
for i in "${!TOKENS[@]}"; do
echo " { OBF_${i}, sizeof(OBF_${i}) },"
done
echo "};"
echo "/* --- END GENERATED OBFUSCATED DEFAULTS --- */"
Loading
Loading