Security Logging skill for Codex
Logs that answer who, what, where, when. Codex designs security logging around the investigation you will actually run: a defined set of authentication, authorization, account and data-access events in a consistent structured schema, central tamper-evident storage with sane retention, and centrally enforced redaction so passwords, tokens and PANs never reach the log store. It then turns those events into detections with owners.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- security-logging Installs to ~/.agents/skills/security-logging/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install security-logging The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project security-logging Installs to .agents/skills/security-logging/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/security-logging.zipunzip -o security-logging.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) security-logging PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $security-logging, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Designs security and audit logging that an investigation can actually use - which events to log (authentication, authorization, account and privilege changes, data access, admin actions), a consistent structured schema, what must never be logged (passwords, tokens, full PANs, secrets), tamper resistance and retention, and detections/alerts built on the events. Use when adding an audit trail, deciding what to log for security, meeting SOC 2 or PCI logging requirements, redacting sensitive data from logs, or turning logs into alerts.
Use it when
- You are adding an audit trail or security event log
- An audit (SOC 2, PCI) requires specific logging and retention
- Secrets or PII are leaking into existing logs
- You want detections built on logged security events
Use something else when
- You are building alert routing and on-call SLOs: use alerting-rules
- You are investigating an incident from existing logs: use log-forensics
How to check the result
Each key event appears in the central store with all standard fields when triggered in staging, a secrets grep over a day of logs finds nothing, and a simulated attack pattern fires its detection.
The mistake it prevents
Logging whole request and response bodies for debugging, which quietly writes passwords, tokens and personal data into the log store.
Done well means: when something goes wrong, the logs answer who did what, to what, from where, and when — and they contain no secrets that turn the log store into the next breach. Log for the investigation you will run under pressure, not for everything just in case.
Decide what to log
Security-relevant events, each with enough context to reconstruct an action:
| Category | Events |
|---|---|
| Authentication | Login success and failure (with reason category), logout, MFA challenge/result, password reset, token issued/refreshed/revoked, session created/expired |
| Authorization | Access denied (403), privilege check failures, step-up auth |
| Account lifecycle | Create, disable, delete, role/permission change, group membership change, email/MFA change |
| Admin and config | Settings changes, feature flags, key rotation, impersonation (“act as user”), data export |
| Sensitive data access | Read/write of regulated records (who read which patient/customer record) |
| Security events | Rate-limit trips, WAF blocks, input validation rejections, integrity check failures |
For each, capture a standard set of fields so events join across services.
{
"timestamp": "2026-10-28T09:14:22.512Z",
"event": "auth.login.failure",
"outcome": "failure",
"reason": "bad_password",
"actor": {"id": "u_8832", "type": "user", "session": "s_a91f"},
"source": {"ip": "203.0.113.9", "user_agent": "...", "request_id": "r_2c7"},
"target": {"type": "account", "id": "u_8832"},
"service": "auth-api",
"env": "prod"
}
- Use UTC, ISO 8601 with milliseconds. Log
request_id/trace ID so one request stitches across services. Include the outcome explicitly; “login” without success/failure is nearly useless. - Use stable, enumerated
eventnames (noun.verb.outcome) so detections match on a field, not a regex over free text. - For “access denied”, log the subject, the resource, and the permission that failed — that is what a privilege-escalation hunt needs.
Never log these
- Passwords, password hashes, security answers.
- Session tokens, API keys, bearer/JWT values, OAuth codes, CSRF tokens, reset tokens.
- Full PANs (PCI: mask to first 6 / last 4 max), CVV (never), full government IDs.
- Secrets, private keys, full request bodies of auth or payment endpoints.
- More personal data than the event needs (log an opaque user ID, not full profiles).
Enforce redaction centrally in the logging layer, not per call site:
REDACT = ("password", "pwd", "secret", "token", "authorization",
"api_key", "apikey", "set-cookie", "ssn", "card", "cvv")
def scrub(obj):
if isinstance(obj, dict):
return {k: ("***" if any(r in k.lower() for r in REDACT) else scrub(v))
for k, v in obj.items()}
if isinstance(obj, (list, tuple)):
return [scrub(v) for v in obj]
return obj
Also scrub at the collector (a Vector/Fluent Bit transform) as defence in depth, and keep tokens out of URLs so they never reach access logs.
Where logs go and how they survive
- Ship off the host to a central store (SIEM, OpenSearch, Loki, CloudWatch, a managed platform). A log only on the compromised box is evidence the attacker can erase.
- Make them append-only/tamper-evident: write-once storage (S3 Object Lock), restricted IAM so the app can write but not delete, and ideally a hash chain or signed batches for high-value audit trails. Separate the account/project that holds logs from the one being logged.
- Retention: set by policy and regulation. Common anchors — PCI DSS requires at least 12 months with 3 months readily available; SOC 2 and many audits expect 12 months. Confirm the number against the org’s policy; it governs storage cost and legal exposure.
- Time sync (NTP) across hosts, or correlation across services falls apart.
Turn events into detections
Logging without alerting is just storage. Build detections on the structured events:
| Detection | Signal |
|---|---|
| Password spraying | Many auth.login.failure across many accounts from few IPs in a short window |
| Brute force | Many failures on one account |
| Impossible travel | Logins for one user from distant geos within minutes |
| Privilege escalation | account.role.change granting admin, or impersonation by non-admin |
| Mass data access | One actor reading far more records than their baseline |
| Log tampering | Gap in sequence, logging agent stopped, integrity check failure |
Alerts need an owner, a runbook link, and tuning so they do not drown the on-call. Route
them to where responders actually look. Alerting mechanics and SLOs live in $alerting-rules
and $incident-response; this skill decides which security events deserve an alert.
Gotchas
- Logging full request/response bodies “for debugging” is the most common way secrets and PII end up in logs. Allowlist fields instead of logging whole objects.
- Verbose logs of failed auth can themselves leak which usernames exist; log a reason category, not “user not found” vs “wrong password” differences that reach the client.
- Over-logging buries signal and runs up cost; sample high-volume non-security logs, never sample security events.
- Client-supplied fields (IP from
X-Forwarded-For, user agent) are attacker-controlled; record them as claimed, and trust the edge/proxy-derived IP for decisions. - Deleting a user must not delete their audit trail; audit logs often outlive the account (reconcile with retention and privacy policy).
Verification
- Trigger each key event (a failed login, a role change, a denied access) in staging and confirm it lands in the central store with all standard fields populated.
- Grep a day of logs for secrets patterns and confirm nothing leaks:
rg -n "password|eyJ|Bearer [A-Za-z0-9._-]{10,}|AKIA|[0-9]{13,16}" sample.log. - Fire a detection by simulating the pattern (for example 50 failed logins) and confirm the alert triggers and routes.
- Confirm append-only: attempt a delete with the app’s own credentials and verify it is denied.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Security Logging"
short_description: "Logs that answer who, what, where, when"
default_prompt: "$security-logging Design an audit log for account and permission changes in our app" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.