@wonsukchoi/crondex 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ id: rss-feed-validate
2
+ version: 1
3
+ name: RSS Feed Validation
4
+ description: >
5
+ Checks your RSS/Atom feed is valid XML and shows the most recent
6
+ item's publish date. Use this if you've ever shipped a broken feed
7
+ and only found out when a reader's app stopped updating.
8
+ category: content
9
+ tags: [rss, feed, content]
10
+ schedule: "0 8 * * *"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ url={{feed_url}};
15
+ xml=$(curl -sL --max-time 10 "$url");
16
+ [ -z "$xml" ] && { echo "ERROR: could not fetch $url"; exit 1; };
17
+ if command -v xmllint >/dev/null 2>&1; then
18
+ echo "$xml" | xmllint --noout - 2>&1 && echo "OK: valid XML" || { echo "INVALID XML"; exit 1; };
19
+ else
20
+ echo "xmllint not installed, skipping strict XML validation";
21
+ fi;
22
+ pubdate=$(echo "$xml" | grep -oE "<pubDate>[^<]+</pubDate>" | head -n1 | sed 's/<[^>]*>//g');
23
+ echo "Most recent item pubDate: ${pubdate:-unknown}"
24
+ variables:
25
+ feed_url:
26
+ default: "https://example.com/feed.xml"
27
+ description: URL of the RSS/Atom feed to check.
28
+ compatible_agents: [generic, claude, codex]
29
+ notes: >
30
+ Zero-token. Only reports the latest pubDate for you to eyeball —
31
+ doesn't flag "stale" automatically since feed cadences vary too much
32
+ for one threshold. Install `xmllint` (libxml2) for strict validation.
@@ -0,0 +1,32 @@
1
+ id: seo-meta-check
2
+ version: 1
3
+ name: SEO Meta Tag Check
4
+ description: >
5
+ Checks a list of pages for missing title, meta description, and
6
+ Open Graph tags. Use this if pages have ever shipped without basic
7
+ SEO/social-preview tags because nobody double-checked.
8
+ category: content
9
+ tags: [seo, content, meta-tags]
10
+ schedule: "0 7 * * 1"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ file={{urls_file}};
15
+ [ -f "$file" ] || { echo "ERROR: $file not found"; exit 1; };
16
+ while IFS= read -r url; do
17
+ [ -z "$url" ] && continue;
18
+ html=$(curl -sL --max-time 10 "$url");
19
+ missing="";
20
+ echo "$html" | grep -qi "<title>" || missing="$missing title";
21
+ echo "$html" | grep -qi 'name="description"' || missing="$missing meta-description";
22
+ echo "$html" | grep -qi 'property="og:title"' || missing="$missing og:title";
23
+ if [ -n "$missing" ]; then echo "MISSING on $url:$missing"; else echo "OK: $url"; fi
24
+ done < "$file"
25
+ variables:
26
+ urls_file:
27
+ default: "./pages.txt"
28
+ description: Plain text file with one page URL per line to check.
29
+ compatible_agents: [generic, claude, codex]
30
+ notes: >
31
+ Zero-token. Basic presence check only (does the tag exist), not content
32
+ quality — pair with a fuller SEO audit for anything deeper.
@@ -0,0 +1,31 @@
1
+ id: api-rate-limit-check
2
+ version: 1
3
+ name: API Rate Limit Check
4
+ description: >
5
+ Checks how many API requests you have left before hitting a rate
6
+ limit. Use this if you've ever gotten a 429 in production because
7
+ nobody was watching the remaining quota.
8
+ category: devops
9
+ tags: [api, rate-limit, monitoring]
10
+ schedule: "*/15 * * * *"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ url={{api_url}};
15
+ headers=$(curl -sI --max-time 10 "$url");
16
+ remaining=$(echo "$headers" | grep -i "^x-ratelimit-remaining:" | awk '{print $2}' | tr -d '\r');
17
+ if [ -z "$remaining" ]; then echo "no rate-limit headers found on $url (nothing to check, or a different header convention)"; exit 0;
18
+ elif [ "$remaining" -le {{warn_threshold}} ]; then echo "WARNING: $url has only $remaining requests remaining"; exit 1;
19
+ else echo "OK: $url has $remaining requests remaining"; fi
20
+ variables:
21
+ api_url:
22
+ default: "https://api.example.com/status"
23
+ description: An endpoint on the API you want to watch — any request that returns rate-limit headers works.
24
+ warn_threshold:
25
+ default: 50
26
+ description: Warn if remaining requests drops to this or below.
27
+ compatible_agents: [generic, claude, codex]
28
+ notes: >
29
+ Assumes the standard `X-RateLimit-Remaining` response header — adjust
30
+ the grep pattern if your API uses a different convention
31
+ (e.g. `RateLimit-Remaining`, a custom header).
@@ -0,0 +1,37 @@
1
+ id: db-backup-verify
2
+ version: 1
3
+ name: Database Backup Restore Verification
4
+ description: >
5
+ Actually restores your latest database backup to a throwaway database
6
+ and checks it's queryable. Use this if `backup-reminder` isn't enough —
7
+ a backup file existing doesn't mean it's actually restorable.
8
+ category: devops
9
+ tags: [database, backup, postgres]
10
+ schedule: "0 5 * * *"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ dump={{dump_path}}; scratch_db={{scratch_db_name}};
15
+ [ -f "$dump" ] || { echo "ERROR: $dump not found"; exit 1; };
16
+ command -v pg_restore >/dev/null 2>&1 || { echo "ERROR: pg_restore not installed"; exit 1; };
17
+ dropdb --if-exists "$scratch_db" 2>/dev/null;
18
+ createdb "$scratch_db" || { echo "ERROR: could not create scratch db $scratch_db"; exit 1; };
19
+ pg_restore -d "$scratch_db" "$dump" >/dev/null 2>&1;
20
+ count=$(psql -d "$scratch_db" -tAc "SELECT count(*) FROM {{checkpoint_table}}" 2>/dev/null);
21
+ dropdb --if-exists "$scratch_db" 2>/dev/null;
22
+ if [ -z "$count" ]; then echo "FAILED: restore or query failed on $dump"; exit 1;
23
+ else echo "OK: restored $dump, $checkpoint_table has $count rows"; fi
24
+ variables:
25
+ dump_path:
26
+ default: "./backups/latest.dump"
27
+ description: Path to the pg_dump/pg_restore-format backup file.
28
+ scratch_db_name:
29
+ default: "backup_verify_scratch"
30
+ description: Throwaway database name used for the test restore (created and dropped each run).
31
+ checkpoint_table:
32
+ default: "users"
33
+ description: A table expected to have rows — used to confirm the restore actually worked.
34
+ compatible_agents: [generic, claude, codex]
35
+ notes: >
36
+ Postgres-specific (pg_restore/psql/createdb/dropdb) — adapt the command
37
+ for other database engines. Needs CREATE/DROP DATABASE privileges.
@@ -0,0 +1,32 @@
1
+ id: dns-record-check
2
+ version: 1
3
+ name: DNS Record Drift Check
4
+ description: >
5
+ Checks that a DNS record still resolves to the value you expect, and
6
+ flags drift. Use this if you've ever had DNS quietly change (an
7
+ expired record, a misconfigured provider) and only found out when
8
+ something broke.
9
+ category: devops
10
+ tags: [dns, monitoring, drift]
11
+ schedule: "0 */6 * * *"
12
+ timezone: "UTC"
13
+ runner: shell
14
+ command: >
15
+ domain={{domain}}; type={{record_type}}; expected="{{expected_value}}";
16
+ command -v dig >/dev/null 2>&1 || { echo "ERROR: dig not installed"; exit 1; };
17
+ actual=$(dig +short "$domain" "$type" | tr '\n' ' ' | sed 's/ *$//');
18
+ if [ -z "$actual" ]; then echo "ERROR: no $type record found for $domain"; exit 1;
19
+ elif echo "$actual" | grep -qF "$expected"; then echo "OK: $domain $type matches expected ($actual)";
20
+ else echo "DRIFT: $domain $type is '$actual', expected to contain '$expected'"; exit 1; fi
21
+ variables:
22
+ domain:
23
+ default: "example.com"
24
+ description: Domain to check.
25
+ record_type:
26
+ default: "A"
27
+ description: DNS record type (A, CNAME, TXT, MX, etc).
28
+ expected_value:
29
+ default: "93.184.216.34"
30
+ description: Value the record should contain — replace with your actual expected record.
31
+ compatible_agents: [generic, claude, codex]
32
+ notes: Zero-token, deterministic. Requires `dig` (part of dnsutils/bind-tools).
@@ -0,0 +1,31 @@
1
+ id: license-compliance-check
2
+ version: 1
3
+ name: Dependency License Compliance Check
4
+ description: >
5
+ Scans your dependencies for licenses you've flagged as risky (like GPL
6
+ or AGPL). Use this if you've ever shipped something and only later
7
+ found out a dependency's license doesn't play well with your product.
8
+ category: devops
9
+ tags: [license, compliance, dependencies]
10
+ schedule: "0 8 * * 1"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ cd {{repo_path}} &&
15
+ if [ -f package.json ]; then
16
+ npx --yes license-checker --summary --excludePrivatePackages 2>/dev/null | grep -iE "{{flagged_licenses}}" && echo "^ flagged licenses found above" || echo "OK: no flagged licenses found";
17
+ else
18
+ echo "no package.json found — this job only supports npm/Node projects currently";
19
+ fi
20
+ variables:
21
+ repo_path:
22
+ default: "."
23
+ description: Path to the repo to check.
24
+ flagged_licenses:
25
+ default: "GPL|AGPL"
26
+ description: Regex alternation of license names to flag.
27
+ compatible_agents: [generic, claude, codex]
28
+ notes: >
29
+ npm/Node projects only (uses `npx license-checker`, downloads on first
30
+ run). For other ecosystems, swap in the equivalent tool
31
+ (pip-licenses, cargo-license, etc.) and adjust the command.
@@ -0,0 +1,28 @@
1
+ id: orphaned-branch-cleanup
2
+ version: 1
3
+ name: Orphaned Branch Cleanup
4
+ description: >
5
+ Lists remote branches already merged into main that nobody's deleted
6
+ yet. Use this if your branch list has become a graveyard nobody wants
7
+ to clean up.
8
+ category: devops
9
+ tags: [git, cleanup, branches]
10
+ schedule: "0 9 * * 1"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ cd {{repo_path}} && git fetch --prune >/dev/null 2>&1;
15
+ merged=$(git branch -r --merged {{base_branch}} | grep -v -E "(\*|{{base_branch}}$)" | sed 's#origin/##');
16
+ if [ -z "$merged" ]; then echo "OK: no merged branches to clean up";
17
+ else echo "Merged into {{base_branch}}, safe to delete:"; echo "$merged"; fi
18
+ variables:
19
+ repo_path:
20
+ default: "."
21
+ description: Path to the repo to check.
22
+ base_branch:
23
+ default: "main"
24
+ description: Branch to check merges against.
25
+ compatible_agents: [generic, claude, codex]
26
+ notes: >
27
+ List-only by design — never deletes anything. Review the list, then
28
+ `git push origin --delete <branch>` yourself for ones you actually want gone.
@@ -0,0 +1,35 @@
1
+ id: queue-depth-check
2
+ version: 1
3
+ name: Queue Depth Check
4
+ description: >
5
+ Checks how backed up a job queue is and warns if it crosses a
6
+ threshold. Use this if a stuck worker has ever let a queue silently
7
+ balloon until someone noticed customers weren't getting emails.
8
+ category: devops
9
+ tags: [queue, redis, monitoring]
10
+ schedule: "*/10 * * * *"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ command -v redis-cli >/dev/null 2>&1 || { echo "ERROR: redis-cli not installed"; exit 1; };
15
+ depth=$(redis-cli -h {{redis_host}} -p {{redis_port}} llen {{queue_key}} 2>/dev/null);
16
+ if [ -z "$depth" ]; then echo "ERROR: could not read queue depth for {{queue_key}}"; exit 1;
17
+ elif [ "$depth" -ge {{threshold}} ]; then echo "WARNING: queue {{queue_key}} depth is $depth (threshold {{threshold}})"; exit 1;
18
+ else echo "OK: queue {{queue_key}} depth is $depth"; fi
19
+ variables:
20
+ redis_host:
21
+ default: "localhost"
22
+ description: Redis host.
23
+ redis_port:
24
+ default: 6379
25
+ description: Redis port.
26
+ queue_key:
27
+ default: "jobs:pending"
28
+ description: Redis list key backing the queue.
29
+ threshold:
30
+ default: 1000
31
+ description: Warn if the queue depth reaches this many items.
32
+ compatible_agents: [generic, claude, codex]
33
+ notes: >
34
+ Redis list-backed queues only (uses LLEN). Adapt the depth-check
35
+ command for RabbitMQ, SQS, or other queue systems.
@@ -0,0 +1,32 @@
1
+ id: invoice-overdue-check
2
+ version: 1
3
+ name: Invoice Overdue Check
4
+ description: >
5
+ Flags invoices that are past due and still unpaid. Use this if you
6
+ freelance or run a small business and invoices have ever slipped
7
+ through the cracks.
8
+ category: finance
9
+ tags: [invoices, accounts-receivable, finance]
10
+ schedule: "0 9 * * *"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ file={{invoices_file}};
15
+ [ -f "$file" ] || { echo "ERROR: $file not found"; exit 1; };
16
+ today=$(date +%s);
17
+ tail -n +2 "$file" | while IFS=, read -r id client amount due paid; do
18
+ [ "$paid" = "yes" ] && continue;
19
+ due_epoch=$(date -j -f %Y-%m-%d "$due" +%s 2>/dev/null || date -d "$due" +%s);
20
+ days=$(( (today-due_epoch)/86400 ));
21
+ if [ "$days" -gt 0 ]; then echo "OVERDUE: invoice $id ($client, \$$amount) — $days day(s) past due ($due)"; fi
22
+ done
23
+ variables:
24
+ invoices_file:
25
+ default: "./invoices.csv"
26
+ description: >
27
+ CSV with header "invoice_id,client,amount,due_date,paid"
28
+ (due_date is YYYY-MM-DD, paid is yes/no).
29
+ compatible_agents: [generic, claude, codex]
30
+ notes: >
31
+ Zero-token. You maintain invoices.csv yourself (mark paid=yes once
32
+ settled) — this doesn't connect to any billing/accounting system.
@@ -0,0 +1,35 @@
1
+ id: saas-seat-audit
2
+ version: 1
3
+ name: Team SaaS Seat Audit
4
+ description: >
5
+ Flags team SaaS seats (Slack, Notion, GitHub, etc.) nobody's actually
6
+ used in a while. Use this if you suspect you're paying for seats of
7
+ people who left or tools nobody opens anymore.
8
+ category: finance
9
+ tags: [saas, seats, team, budget]
10
+ schedule: "0 9 1 * *"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ file={{seats_file}}; unused_days={{unused_days}};
15
+ [ -f "$file" ] || { echo "ERROR: $file not found"; exit 1; };
16
+ today=$(date +%s);
17
+ tail -n +2 "$file" | while IFS=, read -r tool owner cost last_active; do
18
+ last_epoch=$(date -j -f %Y-%m-%d "$last_active" +%s 2>/dev/null || date -d "$last_active" +%s);
19
+ days=$(( (today-last_epoch)/86400 ));
20
+ if [ "$days" -gt "$unused_days" ]; then echo "UNUSED SEAT: $tool - $owner (\$$cost/mo) - inactive $days days"; fi
21
+ done
22
+ variables:
23
+ seats_file:
24
+ default: "./saas-seats.csv"
25
+ description: >
26
+ CSV with header "tool,seat_owner,monthly_cost,last_active_date"
27
+ (YYYY-MM-DD), one seat per row.
28
+ unused_days:
29
+ default: 30
30
+ description: Flag a seat if it hasn't been active in this many days.
31
+ compatible_agents: [generic, claude, codex]
32
+ notes: >
33
+ Zero-token, deterministic. You maintain saas-seats.csv yourself (or
34
+ export last-active data from your SSO/IdP if it tracks that) — this job
35
+ only reads what's in the file, doesn't pull from any SaaS API.
@@ -0,0 +1,33 @@
1
+ id: tax-deadline-reminder
2
+ version: 1
3
+ name: Tax Deadline Reminder
4
+ description: >
5
+ Warns you ahead of upcoming tax deadlines you've listed. Use this if
6
+ quarterly estimated payments or annual filing dates have ever snuck
7
+ up on you.
8
+ category: finance
9
+ tags: [tax, deadlines, finance]
10
+ schedule: "0 9 * * *"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ file={{deadlines_file}}; warn={{warn_days}};
15
+ [ -f "$file" ] || { echo "ERROR: $file not found"; exit 1; };
16
+ today=$(date +%s);
17
+ tail -n +2 "$file" | while IFS=, read -r label due; do
18
+ [ -z "$due" ] && continue;
19
+ due_epoch=$(date -j -f %Y-%m-%d "$due" +%s 2>/dev/null || date -d "$due" +%s);
20
+ days=$(( (due_epoch-today)/86400 ));
21
+ if [ "$days" -ge 0 ] && [ "$days" -le "$warn" ]; then echo "REMINDER: $label due in $days day(s) ($due)"; fi
22
+ done
23
+ variables:
24
+ deadlines_file:
25
+ default: "./tax-deadlines.csv"
26
+ description: CSV with header "label,date" (date is YYYY-MM-DD), one deadline per row.
27
+ warn_days:
28
+ default: 14
29
+ description: Start warning this many days before a deadline.
30
+ compatible_agents: [generic, claude, codex]
31
+ notes: >
32
+ Zero-token. You maintain tax-deadlines.csv yourself with your actual
33
+ jurisdiction's dates — this job doesn't know tax law or deadlines on its own.
@@ -0,0 +1,37 @@
1
+ id: course-progress-checkin
2
+ version: 1
3
+ name: Course Progress Check-in
4
+ description: >
5
+ Compares your current chapter/lesson against a target date and tells
6
+ you if you're on track or falling behind. Use this if you've ever
7
+ signed up for a course and quietly stopped halfway through.
8
+ category: learning
9
+ tags: [learning, goals, progress]
10
+ schedule: "0 9 * * 1"
11
+ timezone: "America/Los_Angeles"
12
+ runner: shell
13
+ command: >
14
+ current={{current_chapter}}; target={{target_chapter}}; target_date={{target_date}};
15
+ today_epoch=$(date +%s);
16
+ target_epoch=$(date -j -f %Y-%m-%d "$target_date" +%s 2>/dev/null || date -d "$target_date" +%s);
17
+ days_left=$(( (target_epoch-today_epoch)/86400 ));
18
+ chapters_left=$(( target - current ));
19
+ if [ "$chapters_left" -le 0 ]; then echo "OK: at or past target chapter $target already.";
20
+ elif [ "$days_left" -le 0 ]; then echo "OVERDUE: target date was $target_date, still $chapters_left chapter(s) to go.";
21
+ elif [ "$chapters_left" -gt "$days_left" ]; then echo "BEHIND: $chapters_left chapter(s) left, only $days_left day(s) until $target_date.";
22
+ else echo "ON TRACK: $chapters_left chapter(s) left, $days_left day(s) until $target_date."; fi
23
+ variables:
24
+ current_chapter:
25
+ default: 1
26
+ description: Chapter/lesson number you're currently on — update this yourself as you progress.
27
+ target_chapter:
28
+ default: 10
29
+ description: Chapter/lesson number you want to finish by target_date.
30
+ target_date:
31
+ default: "2026-12-31"
32
+ description: Date (YYYY-MM-DD) you want to hit target_chapter by.
33
+ compatible_agents: [generic, claude, codex]
34
+ notes: >
35
+ Zero-token. "Behind/on track" is a rough linear heuristic (chapters-left
36
+ vs. days-left), not a real study-pace model. You update current_chapter
37
+ yourself as you go.
@@ -0,0 +1,29 @@
1
+ id: daily-flashcard-review
2
+ version: 1
3
+ name: Daily Flashcard Review
4
+ description: >
5
+ Lists which flashcards are due for review today from a simple deck
6
+ file. Use this if you're learning something with spaced repetition but
7
+ don't want a whole separate app for it.
8
+ category: learning
9
+ tags: [learning, flashcards, spaced-repetition]
10
+ schedule: "0 8 * * *"
11
+ timezone: "America/Los_Angeles"
12
+ runner: shell
13
+ command: >
14
+ deck={{deck_path}};
15
+ [ -f "$deck" ] || { echo "ERROR: $deck not found"; exit 1; };
16
+ today=$(date +%F);
17
+ due=$(awk -F, -v today="$today" 'NR>1 && $3<=today {print $1" -> "$2" (due "$3")"}' "$deck");
18
+ if [ -z "$due" ]; then echo "No cards due today.";
19
+ else echo "Cards due today:"; echo "$due"; fi
20
+ variables:
21
+ deck_path:
22
+ default: "./flashcards.csv"
23
+ description: CSV with header "front,back,next_review_date" (YYYY-MM-DD).
24
+ compatible_agents: [generic, claude, codex]
25
+ notes: >
26
+ Zero-token. You maintain flashcards.csv and update each card's
27
+ next_review_date yourself after reviewing — this is simple date-based
28
+ scheduling, not full SM-2 spaced repetition. This job only tells you
29
+ what's due, it doesn't reschedule cards for you.
@@ -0,0 +1,25 @@
1
+ id: reading-list-nudge
2
+ version: 1
3
+ name: Reading List Nudge
4
+ description: >
5
+ Reminds you what's next on your reading list. Use this if you save
6
+ articles/books to "read later" and later never comes.
7
+ category: learning
8
+ tags: [learning, reading, reminder]
9
+ schedule: "0 9 * * 6"
10
+ timezone: "America/Los_Angeles"
11
+ runner: shell
12
+ command: >
13
+ file={{reading_list_path}};
14
+ [ -f "$file" ] || { echo "ERROR: $file not found"; exit 1; };
15
+ next=$(head -n 1 "$file");
16
+ if [ -z "$next" ]; then echo "Reading list is empty — nice work.";
17
+ else echo "Next up: $next"; fi
18
+ variables:
19
+ reading_list_path:
20
+ default: "./reading-list.txt"
21
+ description: Plain text file, one item per line — the top line is treated as "next."
22
+ compatible_agents: [generic, claude, codex]
23
+ notes: >
24
+ Zero-token. Suggests the top line only — remove an item from
25
+ reading-list.txt yourself once you've read it, or reorder to reprioritize.
@@ -0,0 +1,25 @@
1
+ id: focus-block-reminder
2
+ version: 1
3
+ name: Focus Block Reminder
4
+ description: >
5
+ Nudges you to start a deep-work block at set times. Use this if your
6
+ day fills up with reactive work and real focus time only happens by
7
+ accident.
8
+ category: productivity
9
+ tags: [focus, productivity, deep-work]
10
+ schedule: "0 9,14 * * 1-5"
11
+ timezone: "America/Los_Angeles"
12
+ runner: shell
13
+ command: >
14
+ echo "Focus block: {{block_minutes}} minutes on \"{{focus_task}}\" starting now. Silence notifications."
15
+ variables:
16
+ focus_task:
17
+ default: "deep work"
18
+ description: What to focus on for this block.
19
+ block_minutes:
20
+ default: 50
21
+ description: Length of the focus block in minutes.
22
+ compatible_agents: [generic, claude, codex]
23
+ notes: >
24
+ Zero-token. Purely a nudge — doesn't block notifications/apps itself;
25
+ pair with your OS's focus/DND mode if you want it enforced.
@@ -0,0 +1,37 @@
1
+ id: failed-login-watch
2
+ version: 1
3
+ name: Failed Login Watch
4
+ description: >
5
+ Watches an auth log for a burst of failed login attempts and alerts if
6
+ it crosses a threshold. Use this if you run a server exposed to SSH and
7
+ want an early warning for brute-force attempts.
8
+ category: security
9
+ tags: [security, auth, monitoring]
10
+ schedule: "*/15 * * * *"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ log={{auth_log_path}}; state={{state_file}}; threshold={{fail_threshold}};
15
+ [ -f "$log" ] || { echo "ERROR: $log not found"; exit 1; };
16
+ total=$(grep -c "Failed password" "$log");
17
+ last=$(cat "$state" 2>/dev/null || echo 0);
18
+ new=$(( total - last ));
19
+ [ "$new" -lt 0 ] && new=0;
20
+ echo "$total" > "$state";
21
+ if [ "$new" -ge "$threshold" ]; then echo "WARNING: $new new failed login attempt(s) since last check (threshold $threshold)"; exit 1;
22
+ else echo "OK: $new new failed login attempt(s) since last check"; fi
23
+ variables:
24
+ auth_log_path:
25
+ default: "/var/log/auth.log"
26
+ description: Path to the auth log to watch (Linux-style).
27
+ state_file:
28
+ default: "./.failed-login-watch-state"
29
+ description: File this job uses to remember the last count seen, so it only alerts on new failures.
30
+ fail_threshold:
31
+ default: 5
32
+ description: Alert if this many new failed attempts appear since the last check.
33
+ compatible_agents: [generic, claude, codex]
34
+ notes: >
35
+ Linux-oriented (/var/log/auth.log). On macOS, use
36
+ `log show --predicate 'eventMessage contains "authentication failure"'`
37
+ instead and adapt the command — this one won't work there as-is.
@@ -0,0 +1,30 @@
1
+ id: firewall-rule-diff
2
+ version: 1
3
+ name: Firewall Rule Diff
4
+ description: >
5
+ Diffs your current firewall rules against a saved baseline and flags
6
+ any drift. Use this if a firewall rule has ever changed without
7
+ anyone remembering who did it or why.
8
+ category: security
9
+ tags: [security, firewall, drift]
10
+ schedule: "0 6 * * *"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ baseline={{baseline_path}};
15
+ if command -v ufw >/dev/null 2>&1; then current=$(sudo ufw status verbose 2>/dev/null);
16
+ elif command -v iptables >/dev/null 2>&1; then current=$(sudo iptables -S 2>/dev/null);
17
+ else echo "ERROR: no supported firewall tool found (ufw/iptables)"; exit 1; fi;
18
+ if [ ! -f "$baseline" ]; then echo "$current" > "$baseline"; echo "no baseline found — saved current rules as the new baseline"; exit 0; fi;
19
+ if diff -q <(echo "$current") "$baseline" >/dev/null 2>&1; then echo "OK: firewall rules match baseline";
20
+ else echo "DRIFT: firewall rules differ from baseline:"; diff <(echo "$current") "$baseline"; exit 1; fi
21
+ variables:
22
+ baseline_path:
23
+ default: "./firewall-baseline.txt"
24
+ description: File storing the known-good firewall rule snapshot.
25
+ compatible_agents: [generic, claude, codex]
26
+ notes: >
27
+ Requires sudo access to read ufw/iptables rules (configure passwordless
28
+ sudo for this specific read-only command if running unattended). First
29
+ run saves the baseline; later runs diff against it. Requires bash
30
+ (process substitution).
@@ -0,0 +1,36 @@
1
+ id: open-port-check
2
+ version: 1
3
+ name: Expected Open Ports Check
4
+ description: >
5
+ Checks that the ports you expect to be open on a host actually are, and
6
+ flags any that aren't. Use this if a service has ever silently stopped
7
+ listening and you found out from a user instead of a monitor.
8
+ category: security
9
+ tags: [security, network, ports]
10
+ schedule: "0 */4 * * *"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ host={{host}}; fail=0;
15
+ for p in $(echo "{{expected_open_ports}}" | tr ',' ' '); do
16
+ if command -v nc >/dev/null 2>&1; then
17
+ nc -z -w2 "$host" "$p" 2>/dev/null && echo "OK: $host:$p open" || { echo "MISSING: $host:$p expected open but isn't"; fail=1; };
18
+ else
19
+ (echo > /dev/tcp/$host/$p) 2>/dev/null && echo "OK: $host:$p open" || { echo "MISSING: $host:$p expected open but isn't"; fail=1; };
20
+ fi
21
+ done;
22
+ exit $fail
23
+ variables:
24
+ host:
25
+ default: "localhost"
26
+ description: Host to check.
27
+ expected_open_ports:
28
+ default: "22,80,443"
29
+ description: Comma-separated list of ports that should be open.
30
+ compatible_agents: [generic, claude, codex]
31
+ notes: >
32
+ Zero-token. Checks only the ports you list — not a full port scan. Use
33
+ nmap for broader unexpected-open-port discovery. The nc branch (used on
34
+ virtually every system) is the tested path; the /dev/tcp fallback only
35
+ matters if nc is missing and requires bash specifically — it won't work
36
+ under dash/POSIX sh.
@@ -0,0 +1,28 @@
1
+ id: secrets-scan
2
+ version: 1
3
+ name: Committed Secrets Scan
4
+ description: >
5
+ Scans a repo for accidentally committed API keys, tokens, and private
6
+ keys. Use this if you want a safety net for the "oops, committed a key"
7
+ mistake everyone makes eventually.
8
+ category: security
9
+ tags: [security, secrets, scanning]
10
+ schedule: "0 3 * * *"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ cd {{repo_path}} &&
15
+ if command -v gitleaks >/dev/null 2>&1; then
16
+ gitleaks detect --source . --no-git -v;
17
+ else
18
+ echo "gitleaks not installed, falling back to basic pattern matching";
19
+ grep -rnE "(AKIA[0-9A-Z]{16}|-----BEGIN (RSA|EC|DSA|OPENSSH) PRIVATE KEY-----|api[_-]?key['\"]?[[:space:]]*[:=][[:space:]]*['\"][A-Za-z0-9_-]{16,})" --exclude-dir=.git --exclude-dir=node_modules . || echo "no matches found";
20
+ fi
21
+ variables:
22
+ repo_path:
23
+ default: "."
24
+ description: Path to the repo to scan.
25
+ compatible_agents: [generic, claude, codex]
26
+ notes: >
27
+ Zero-token. Prefers `gitleaks` if installed (far more accurate); the
28
+ fallback regex is best-effort and will miss many patterns or false-positive.
@@ -0,0 +1,35 @@
1
+ id: sudo-usage-audit
2
+ version: 1
3
+ name: Sudo Usage Audit
4
+ description: >
5
+ Reports new sudo commands run since the last check. Use this if you
6
+ want to notice unexpected privilege escalation on a shared or
7
+ internet-facing server.
8
+ category: security
9
+ tags: [security, sudo, audit]
10
+ schedule: "0 * * * *"
11
+ timezone: "UTC"
12
+ runner: shell
13
+ command: >
14
+ log={{auth_log_path}}; state={{state_file}};
15
+ [ -f "$log" ] || { echo "ERROR: $log not found"; exit 1; };
16
+ total=$(grep -c "sudo:.*COMMAND=" "$log");
17
+ last=$(cat "$state" 2>/dev/null || echo 0);
18
+ new=$(( total - last ));
19
+ [ "$new" -lt 0 ] && new=0;
20
+ echo "$total" > "$state";
21
+ if [ "$new" -gt 0 ]; then
22
+ echo "New sudo commands since last check ($new):";
23
+ grep "sudo:.*COMMAND=" "$log" | tail -n "$new";
24
+ else echo "OK: no new sudo usage since last check"; fi
25
+ variables:
26
+ auth_log_path:
27
+ default: "/var/log/auth.log"
28
+ description: Path to the auth log (Linux-style).
29
+ state_file:
30
+ default: "./.sudo-audit-state"
31
+ description: File this job uses to remember the last count seen, so it only reports new entries.
32
+ compatible_agents: [generic, claude, codex]
33
+ notes: >
34
+ Linux-oriented (/var/log/auth.log with sudo entries). On macOS use
35
+ `log show --predicate 'process == "sudo"'` instead and adapt the command.