Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Remove Secrets from Hosted Mac Automation Logs

Agent logs capture everything that goes wrong — and sometimes everything that goes right, including the API key that authorized the call. The fix is not a cleanup script after the fact; it is a structured log schema that never admits secret values, combined with a test corpus you run before the agent reaches production.

Choose a Figment Plan for the Agent Host

The Figment virtual Intel macOS environment at $29/month (2 vCPU, 8 GiB RAM, 80 GiB disk) is a direct, low-cost host for agents that write logs locally and ship them to a central store. It runs a real macOS userland on Linux/KVM, so compatible Intel toolchains (Xcode 26 or earlier) and standard CLI tools work the same way they do on a physical Intel Mac; it is not a jump host pointed at some other machine. Access is through the browser console today, which is adequate for editing a launchd plist, writing log-rotation config, and tailing a file.

Step up to an M-series Mac from $95/month when the agent needs ARM execution, GPU acceleration, or heavier concurrency. Specific chip, RAM and disk are confirmed in the configuration quote — do not assume a particular M-series part at the entry price. The Managed Build Mac at $249/month adds one M-series Mac, CI setup, backups, monitoring and 30 minutes of monthly support, which you can point at the agent logs as an additional signal.

Figment competes on the specific managed-CI work in that $249 plan, not on raw hourly rentals. If all you need is a tiny host that writes JSON log lines and runs a nightly grep, start on the $29 VM and request an upgrade when the workload needs it. All prices here are the monthly list prices on /mac-hosting; taxes and fees apply as stated in the actual offer.

Where Agent Logs Land on a Hosted Mac

A launchd user agent writes to whatever paths you set in StandardOutPath and StandardErrorPath. If you leave them unset, output is discarded. Always set both and keep them inside a directory the agent user owns. See Apple's Creating Launchd Jobs for the full plist key list, including StartCalendarInterval for scheduled runs.

Three log destinations to keep separate in your head:

DestinationPath patternRedaction needed?
Agent stdout/stderr/Users/agent/logs/agent.log (your choice)Yes — this is the file you control
macOS Unified LogIn-memory ring buffer; persists to /var/db/diagnostics/Conditional — the private specifier hides values
HTTP debug outputurllib3, requests verbose mode, curl -vYes — Authorization headers appear here

Apple's guide to generating log messages documents that dynamic string arguments to os_log and Logger are redacted as <private> by default on release builds; %{public}s opts an argument back in. That behaviour applies to the Unified Log, not to files you write yourself — nothing in os_log protects a line you append to agent.log with logging.FileHandler.

Safe Structured-Log Field List

Have the agent emit one JSON object per line and decide ahead of time which fields are allowed at all.

FieldTypeSafe to log?Notes
timestampISO 8601 stringYesAlways UTC
jobstringYesAgent job name
run_idstringYesUUID per invocation
statusint / stringYesHTTP status or ok/error
modelstringYese.g. gpt-4o-mini
input_tokensintYesCount only
output_tokensintYesCount only
spent_usdfloat (4 d.p.)YesCost figure, not a key
tool_namestringYesWhich tool was called
tool_args_summarystringConditionalSummarise; scrub paths with usernames or PII
error_typestringYesException class name
error_messagestringConditionalPass through the redactor before emitting
api_keystringNeverKeep in Keychain or the CI secret manager
authorization_headerstringNeverDo not log HTTP headers at debug in production
request_bodystringNeverMay contain the user's prompt or PII
response_bodystringNeverMay contain sensitive model output
env_dumpstringNeverNever serialise the whole environment to a log

A line that uses only the allowed fields:

{"timestamp":"2026-11-10T14:32:01Z","job":"openai-agent","run_id":"a3f8b2c1","status":200,"model":"gpt-4o-mini","input_tokens":1240,"output_tokens":380,"spent_usd":0.0005,"tool_name":"fetch_records","error_type":null}

Redact at Write Time

The reliable strategy is never to admit a secret value into the log stream in the first place. For Python agents using the standard logging module:

import logging, re

# Quiet the libraries that like to dump headers at DEBUG.
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)

# Patterns are applied in order. Test every change against the corpus below.
_SECRET_PATTERNS = [
    (re.compile(r"sk-[A-Za-z0-9\-_]{20,}"), "sk-***"),
    (re.compile(r"ghp_[A-Za-z0-9]{36,}"), "ghp_***"),
    (re.compile(r"(?<=Bearer )[A-Za-z0-9\-_.~+/=]{10,}"), "***"),
]

class RedactingFormatter(logging.Formatter):
    def format(self, record: logging.LogRecord) -> str:
        msg = super().format(record)
        for pattern, replacement in _SECRET_PATTERNS:
            msg = pattern.sub(replacement, msg)
        return msg

handler = logging.FileHandler("/Users/agent/logs/agent.log")
handler.setFormatter(RedactingFormatter("%(message)s"))
logging.getLogger().addHandler(handler)

Add a pattern for every credential format the agent handles. The three above cover OpenAI-style sk- keys, GitHub personal access tokens, and generic Bearer tokens.

Redaction Test Corpus

A test corpus is a small file of sample strings — some that must be redacted, some that must pass through untouched — that you run the formatter against before the agent reaches production. Commit it with the code and run it in CI.

# test_corpus.txt
# Format: LINE_TYPE | raw input | expected output after redaction
#
# Lines that MUST be redacted:
SECRET | Authorization: Bearer sk-proj-AbCdEfGhIjKlMnOpQrStUv1234567890ab | Authorization: Bearer sk-***
SECRET | api_key="sk-ant-api03-xyzXYZ1234567890abcdefABCDEF" | api_key="sk-***"
SECRET | OPENAI_API_KEY=sk-proj-testkey12345678901234567890 | OPENAI_API_KEY=sk-***
SECRET | token: ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | token: ghp_***
SECRET | Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp | Bearer ***
#
# Lines that MUST NOT be changed:
SAFE   | status=200 model=gpt-4o-mini spent_usd=0.0005 | status=200 model=gpt-4o-mini spent_usd=0.0005
SAFE   | error_type=RateLimitError | error_type=RateLimitError
SAFE   | input_tokens=1240 output_tokens=380 | input_tokens=1240 output_tokens=380
SAFE   | job=openai-agent run_id=a3f8b2c1 | job=openai-agent run_id=a3f8b2c1

Run the corpus through the formatter in a test. This must exit non-zero on any mismatch:

# test_redaction.py
import sys
from agent_logging import _SECRET_PATTERNS  # your patterns module

def apply_redaction(text: str) -> str:
    for pattern, replacement in _SECRET_PATTERNS:
        text = pattern.sub(replacement, text)
    return text

failures, checked = [], 0
with open("test_corpus.txt") as f:
    for line in f:
        line = line.strip()
        if not line or line.startswith("#") or "|" not in line:
            continue
        line_type, raw, expected = [p.strip() for p in line.split("|", 2)]
        checked += 1
        result = apply_redaction(raw)
        if result != expected:
            failures.append(f"{line_type}: input={raw!r} expected={expected!r} got={result!r}")

if failures:
    print("\n".join(failures))
    sys.exit(1)
print(f"All {checked} corpus lines passed.")

Every new provider integration earns one or two new corpus lines. The test fails loudly if a new key shape leaks through.

Post-Process Existing Logs

For log files written before the formatter was in place, rewrite them once before archiving or sharing:

#!/bin/bash
# redact_existing_log.sh
set -euo pipefail
INPUT="$1"
OUTPUT="${INPUT%.log}-redacted.log"

sed -E \
  -e 's/sk-[A-Za-z0-9_-]{20,}/sk-***/g' \
  -e 's/ghp_[A-Za-z0-9]{36,}/ghp_***/g' \
  -e 's/(Bearer )[A-Za-z0-9_.~+/-]{10,}/\1***/g' \
  "$INPUT" > "$OUTPUT"

diff -u "$INPUT" "$OUTPUT" | head -40
echo "Redacted log written to: $OUTPUT"

The diff preview shows exactly what changed; keep the original only until you have reviewed it.

Nightly Verification with launchd

Schedule a nightly scan that greps current log files for the known secret shapes and writes a line to a scan log. Use a StartCalendarInterval key at 03:00:

#!/bin/bash
# nightly_secret_scan.sh
set -euo pipefail
LOG_DIR=/Users/agent/logs
PATTERNS='sk-[A-Za-z0-9_-]{20,}|ghp_[A-Za-z0-9]{36,}|Bearer [A-Za-z0-9_.~+/-]{15,}'
STAMP="$(date -u +%FT%TZ)"

if grep -rqE "$PATTERNS" "$LOG_DIR" 2>/dev/null; then
  echo "$STAMP SECRET LEAK DETECTED in $LOG_DIR" >> "$LOG_DIR/security-scan.log"
  exit 1
fi
echo "$STAMP scan clean" >> "$LOG_DIR/security-scan.log"

A hit here is a credential-rotation event, not a redaction event: rotate the leaked key in the issuing system first, then rewrite the affected log files with the post-processor above.

Start on a Figment Mac

The $29 virtual Intel macOS plan runs this whole setup — launchd job, Python formatter, corpus test, nightly scan — on a Mac environment you control. If you want the nightly scan results piped into managed monitoring with human follow-up, the Managed Build Mac at $249/month includes monitoring and 30 minutes of monthly support. Browse Mac hosting plans or request your configuration.

Related reading


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms