Template · Engineering

Security advisory triage

Each week Jimothy runs your audit command in a fresh worktree. Claude Code reads every advisory and checks whether your code reaches the vulnerable function, so the report separates real exposure from noise. The report goes to Slack either way; when there’s something reachable and fixable, it upgrades those packages, your tests run, and you approve the PR.

Download .json

Schedule0 6 * * 2Security advisory triageTue · 06:00
Pipeline
  1. Run audit Shell
  2. Check reachability Claude Code · Opus
  3. Post report Shell
  4. Anything to fix? Condition
  5. Upgrade packages Claude Code · Sonnet
  6. Run tests Shell
  7. Approve upgrade You
  8. Open pull request Shell
Pull requestacme/app#944Upgrade undici and jsonwebtoken (2 reachable advisories)Open
The pipeline

What each step does

  1. Run audit

    Shell

    Runs the audit command. Audits exit with an error when they find something, so the step always succeeds.

  2. Check reachability

    Claude Code · Opus

    Checks each advisory against your code and decides whether it matters and how to fix it.

  3. Post report

    Shell

    Posts the triage report to Slack.

  4. Anything to fix?

    Condition

    Continues only when a reachable advisory can be fixed with a patch or minor upgrade.

  5. Upgrade packages

    Claude Code · Sonnet

    Upgrades only the reachable, fixable packages and adjusts code if the upgrade needs it.

  6. Run tests

    Shell

    Runs your tests and sends failures back to the upgrade step.

    If it fails, goes back to Upgrade packages, up to 2 times.

  7. Approve upgrade

    You

    Shows you the report before the upgrade is pushed.

  8. Open pull request

    Shell

    Pushes the branch and opens a pull request with the triage report.

Set it up

From copy to first run

  1. Copy the pipeline JSON with the button above.
  2. In Jimothy, open Pipelines → New pipeline, paste it under “Or import a pipeline JSON” and click Import.
  3. Under Workspace, set Local repository to your clone.
  4. Fill in the variables below under Pipeline settings → Variables.
  5. Triggers → Add trigger → Schedule, cron 0 6 * * 2 (Tuesdays at 06:00).
Variables

What to fill in

The prompts and commands read these as {{vars.<name>}}. The examples are placeholders: replace them with your own.

VariableWhat to put in itExample
auditCommandYour dependency audit command, e.g. npm audit --json, pip-audit -f json or cargo audit --json.npm audit --json
testCommandThe command that runs your test suite.npm test
slackWebhookUrlA Slack incoming webhook for the security or engineering channel.—
Show the pipeline JSON
{
  "jimothyPipeline": 1,
  "name": "Security advisory triage",
  "description": "Audits dependencies, checks which advisories your code actually reaches, and opens a PR fixing them.",
  "icon": "bug",
  "color": "#c2410c",
  "repo": {
    "mode": "worktree",
    "localPath": "",
    "baseBranch": "main",
    "branchTemplate": "jimothy/security-{{run.number}}"
  },
  "concurrency": 1,
  "variables": {
    "auditCommand": "npm audit --json",
    "testCommand": "npm test",
    "slackWebhookUrl": ""
  },
  "steps": [
    {
      "id": "audit",
      "name": "Run audit",
      "type": "shell",
      "description": "Runs the audit command. Audits exit with an error when they find something, so the step always succeeds.",
      "timeoutMinutes": 10,
      "command": "sh -c {{vars.auditCommand}} 2>&1 | head -c 60000; true"
    },
    {
      "id": "triage",
      "name": "Check reachability",
      "type": "agent",
      "description": "Checks each advisory against your code and decides whether it matters and how to fix it.",
      "harnessId": "claude-code",
      "model": "opus",
      "dependsOn": [
        "audit"
      ],
      "timeoutMinutes": 40,
      "prompt": "Dependency audit output:\n{{steps.audit.output}}\n\nFor each advisory, read the advisory and check this repository: is the vulnerable package used at runtime or only in development, and does our code call the vulnerable function or path? Do not modify any files.\n\nWrite a Slack report (*bold*, - bullets) with three groups: *Reachable* (with the file and line that reaches it), *Not reachable* (one line why), *Dev only*. For each reachable one, give the fixed version and whether upgrading is a patch, minor or major change.\n\nEnd with exactly one line: FIXABLE: yes if at least one reachable advisory can be fixed by a patch or minor upgrade, otherwise FIXABLE: no."
    },
    {
      "id": "report",
      "name": "Post report",
      "type": "shell",
      "description": "Posts the triage report to Slack.",
      "dependsOn": [
        "triage"
      ],
      "timeoutMinutes": 2,
      "command": "printf '{\"text\":%s}' {{steps.triage.output | json}} | curl -fsS -X POST -H 'Content-Type: application/json' --data @- {{vars.slackWebhookUrl}}"
    },
    {
      "id": "fixable",
      "name": "Anything to fix?",
      "type": "condition",
      "description": "Continues only when a reachable advisory can be fixed with a patch or minor upgrade.",
      "dependsOn": [
        "triage"
      ],
      "condition": {
        "match": "all",
        "rules": [
          {
            "value": "{{steps.triage.output}}",
            "op": "matches",
            "compare": "FIXABLE:\\s*yes"
          }
        ]
      }
    },
    {
      "id": "fix",
      "name": "Upgrade packages",
      "type": "agent",
      "description": "Upgrades only the reachable, fixable packages and adjusts code if the upgrade needs it.",
      "harnessId": "claude-code",
      "model": "sonnet",
      "dependsOn": [
        "fixable"
      ],
      "timeoutMinutes": 40,
      "prompt": "Triage report:\n{{steps.triage.output}}\n\nUpgrade only the packages listed as reachable and fixable by a patch or minor upgrade, to the lowest fixed version. Update the lockfile, adjust code if the upgrade requires it, and commit with a message listing each advisory fixed.{{#if steps.test.output}}\n\nThe tests failed after your last attempt:\n{{steps.test.output | truncate:6000}}{{/if}}"
    },
    {
      "id": "test",
      "name": "Run tests",
      "type": "shell",
      "description": "Runs your tests and sends failures back to the upgrade step.",
      "dependsOn": [
        "fix"
      ],
      "timeoutMinutes": 30,
      "loopBackTo": "fix",
      "maxLoops": 2,
      "command": "{{vars.testCommand | raw}}"
    },
    {
      "id": "approve",
      "name": "Approve upgrade",
      "type": "approval",
      "description": "Shows you the report before the upgrade is pushed.",
      "dependsOn": [
        "test"
      ],
      "approvalMessage": "Open a PR with these security upgrades?\n\n{{steps.triage.output}}"
    },
    {
      "id": "pr",
      "name": "Open pull request",
      "type": "shell",
      "description": "Pushes the branch and opens a pull request with the triage report.",
      "dependsOn": [
        "approve"
      ],
      "timeoutMinutes": 5,
      "command": "git push -u origin HEAD && gh pr create --base {{run.baseBranch}} --title \"Fix reachable security advisories\" --body {{steps.triage.output}}"
    }
  ]
}