audit-logging by claude-dev-suite/claude-dev-suite
npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill audit-logginginterface AuditEvent {
id: string;
timestamp: string; // ISO 8601
actor: {
id: string;
type: 'user' | 'system' | 'api_key';
ip?: string;
userAgent?: string;
};
action: string; // 'user.created', 'order.deleted'
resource: {
type: string; // 'user', 'order'
id: string;
};
changes?: { // Before/after for updates
field: string;
before: unknown;
after: unknown;
}[];
metadata?: Record<string, unknown>;
}
class AuditService {
async log(event: Omit<AuditEvent, 'id' | 'timestamp'>): Promise<void> {
const auditEntry: AuditEvent = {
id: randomUUID(),
timestamp: new Date().toISOString(),
...event,
};
// Write to append-only table
await db.auditLog.create({ data: auditEntry });
// Optionally stream to external system
await this.eventStream?.publish('audit', auditEntry);
}
}
// Usage in service layer
async function updateUser(userId: string, data: UpdateUserDto, actor: Actor) {
const before = await db.user.findUnique({ where: { id: userId } });
const after = await db.user.update({ where: { id: userId }, data });
await audit.log({
actor: { id: actor.id, type: 'user', ip: actor.ip },
action: 'user.updated',
resource: { type: 'user', id: userId },
changes: diffFields(before, after, ['name', 'email', 'role']),
});
return after;
}
function diffFields(before: any, after: any, fields: string[]) {
return fields
.filter((f) => before[f] !== after[f])
.map((f) => ({ field: f, before: before[f], after: after[f] }));
}
interface AuditEvent {
id: string;
timestamp: string; // ISO 8601
actor: {
id: string;
type: 'user' | 'system' | 'api_key';
ip?: string;
userAgent?: string;
};
action: string; // 'user.created', 'order.deleted'
resource: {
type: string; // 'user', 'order'
id: string;
};
changes?: { // Before/after for updates
field: string;
before: unknown;
after: unknown;
}[];
metadata?: Record<string, unknown>;
}
class AuditService {
async log(event: Omit<AuditEvent, 'id' | 'timestamp'>): Promise<void> {
const auditEntry: AuditEvent = {
id: randomUUID(),
timestamp: new Date().toISOString(),
...event,
};
// Write to append-only table
await db.auditLog.create({ data: auditEntry });
// Optionally stream to external system
await this.eventStream?.publish('audit', auditEntry);
}
}
// Usage in service layer
async function updateUser(userId: string, data: UpdateUserDto, actor: Actor) {
const before = await db.user.findUnique({ where: { id: userId } });
const after = await db.user.update({ where: { id: userId }, data });
await audit.log({
actor: { id: actor.id, type: 'user', ip: actor.ip },
action: 'user.updated',
resource: { type: 'user', id: userId },
changes: diffFields(before, after, ['name', 'email', 'role']),
});
return after;
}
function diffFields(before: any, after: any, fields: string[]) {
return fields
.filter((f) => before[f] !== after[f])
.map((f) => ({ field: f, before: before[f], after: after[f] }));
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
function auditMiddleware(action: string) {
return (req: Request, res: Response, next: NextFunction) => {
const originalJson = res.json.bind(res);
res.json = (body) => {
audit.log({
actor: { id: req.user?.id ?? 'anonymous', type: 'user', ip: req.ip },
action,
resource: { type: action.split('.')[0], id: req.params.id ?? body?.id },
});
return originalJson(body);
};
next();
};
}
app.delete('/api/users/:id', auditMiddleware('user.deleted'), deleteUserHandler);
CREATE TABLE audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
timestamp TIMESTAMPTZ NOT NULL DEFAULT now(),
actor_id TEXT NOT NULL,
actor_type TEXT NOT NULL,
actor_ip INET,
action TEXT NOT NULL,
resource_type TEXT NOT NULL,
resource_id TEXT NOT NULL,
changes JSONB,
metadata JSONB
);
-- Append-only: revoke UPDATE and DELETE
REVOKE UPDATE, DELETE ON audit_logs FROM app_user;
-- Indexes for common queries
CREATE INDEX idx_audit_actor ON audit_logs (actor_id, timestamp DESC);
CREATE INDEX idx_audit_resource ON audit_logs (resource_type, resource_id, timestamp DESC);
CREATE INDEX idx_audit_action ON audit_logs (action, timestamp DESC);
| 反模式 | 修复方案 |
|---|---|
| 在事务内部记录日志(失败则无日志) | 在成功提交后记录日志 |
| 可变的审计表 | 撤销 UPDATE/DELETE 权限,采用仅追加模式 |
| 缺少操作者身份信息 | 始终捕获执行操作的人员信息 |
| 记录敏感字段值 | 对个人身份信息进行脱敏处理(email → j***@example.com) |
| 无保留策略 | 按月分区,保留期后归档 |
每周安装量
1
代码仓库
首次出现
3 天前
安全审计
安装于
amp1
cline1
openclaw1
opencode1
cursor1
kimi-cli1
function auditMiddleware(action: string) {
return (req: Request, res: Response, next: NextFunction) => {
const originalJson = res.json.bind(res);
res.json = (body) => {
audit.log({
actor: { id: req.user?.id ?? 'anonymous', type: 'user', ip: req.ip },
action,
resource: { type: action.split('.')[0], id: req.params.id ?? body?.id },
});
return originalJson(body);
};
next();
};
}
app.delete('/api/users/:id', auditMiddleware('user.deleted'), deleteUserHandler);
CREATE TABLE audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
timestamp TIMESTAMPTZ NOT NULL DEFAULT now(),
actor_id TEXT NOT NULL,
actor_type TEXT NOT NULL,
actor_ip INET,
action TEXT NOT NULL,
resource_type TEXT NOT NULL,
resource_id TEXT NOT NULL,
changes JSONB,
metadata JSONB
);
-- Append-only: revoke UPDATE and DELETE
REVOKE UPDATE, DELETE ON audit_logs FROM app_user;
-- Indexes for common queries
CREATE INDEX idx_audit_actor ON audit_logs (actor_id, timestamp DESC);
CREATE INDEX idx_audit_resource ON audit_logs (resource_type, resource_id, timestamp DESC);
CREATE INDEX idx_audit_action ON audit_logs (action, timestamp DESC);
| Anti-Pattern | Fix |
|---|---|
| Logging inside transaction (fails = no log) | Log after successful commit |
| Mutable audit table | Revoke UPDATE/DELETE, use append-only |
| Missing actor identity | Always capture who performed the action |
| Logging sensitive field values | Redact PII (email → j***@example.com) |
| No retention policy | Partition by month, archive after retention period |
Weekly Installs
1
Repository
First Seen
3 days ago
Security Audits
Installed on
amp1
cline1
openclaw1
opencode1
cursor1
kimi-cli1
xdrop 文件传输脚本:Bun 环境下安全上传下载工具,支持加密分享
28,800 周安装