webhook

The atago project wrote these specs on its own initiative and runs them in its own CI, to exercise atago against a real program. They are not webhook’s official test suite, and the webhook project is not affiliated with atago.

Summary #

1 suite · 5 scenarios

Contents #

webhook (self-hosted webhook receiver) #

webhook turns an HTTP request into a command execution, which makes it a piece of security-relevant plumbing: everything about it is a question of what should and should not run.

The happy path is verified by its effect, not its response — the triggered command writes a file, and that file is checked on disk.

The refusals are pinned just as carefully. When a trigger rule is not satisfied the command must not run at all, which is asserted by the absence of its effect rather than by the status code. An HMAC signature is verified against an independently computed value, so a forged one is rejected. The allowed-methods list rejects the wrong verb. And a command that exits non-zero surfaces as a 500 instead of being reported as success.

Source: test/e2e/thirdparty/webhook/webhook.atago.yaml

Scenario: a post runs the command, returns its output, and writes its file #

Given #

  • Background service webhook is started: webhook -hooks hooks.json -ip 127.0.0.1 -port 18094.
  • Fixture file handler.sh is created.
  • Fixture file hooks.json is created.

Inputs #

Fixture handler.sh:

#!/bin/sh
printf 'handled %s\n' "$1" > out.txt
printf 'ran for %s' "$1"

Fixture hooks.json:

[
  {
    "id": "greet",
    "execute-command": "${workdir}/handler.sh",
    "command-working-directory": "${workdir}",
    "include-command-output-in-response": true,
    "pass-arguments-to-command": [
      { "source": "payload", "name": "name" }
    ]
  }
]

When #

# HTTP POST /hooks/greet
# HTTP POST /hooks/nope

Then #

  • after HTTP POST /hooks/greet:
    • HTTP status is 200
    • body contains ran for Alice
    • file out.txt contains handled Alice
  • after HTTP POST /hooks/nope:
    • HTTP status is 404

Scenario: a trigger-rule gates execution and blocks it when unsatisfied #

Given #

  • Background service webhook is started: webhook -hooks hooks.json -ip 127.0.0.1 -port 18095.
  • Fixture file handler.sh is created.
  • Fixture file hooks.json is created.

Inputs #

Fixture handler.sh:

#!/bin/sh
printf 'executed\n' > ran.txt
printf 'ok'

Fixture hooks.json:

[
  {
    "id": "guarded",
    "execute-command": "${workdir}/handler.sh",
    "command-working-directory": "${workdir}",
    "include-command-output-in-response": true,
    "trigger-rule": {
      "match": {
        "type": "value",
        "value": "open-sesame",
        "parameter": { "source": "payload", "name": "token" }
      }
    }
  }
]

When #

# HTTP POST /hooks/guarded
# HTTP POST /hooks/guarded

Then #

  • after HTTP POST /hooks/guarded:
    • HTTP status is 200
    • body contains Hook rules were not satisfied
    • file ran.txt does not exist
  • after HTTP POST /hooks/guarded:
    • HTTP status is 200
    • file ran.txt contains executed

Scenario: an HMAC signature is verified against an independent oracle #

Given #

  • Background service webhook is started: webhook -hooks hooks.json -ip 127.0.0.1 -port 18096.
  • Fixture file handler.sh is created.
  • Fixture file hooks.json is created.

Inputs #

Fixture handler.sh:

#!/bin/sh
printf 'signed\n' > ran.txt
printf 'ok'

Fixture hooks.json:

[
  {
    "id": "signed",
    "execute-command": "${workdir}/handler.sh",
    "command-working-directory": "${workdir}",
    "include-command-output-in-response": true,
    "trigger-rule": {
      "match": {
        "type": "payload-hmac-sha256",
        "secret": "atago-demo-secret",
        "parameter": { "source": "header", "name": "X-Hub-Signature-256" }
      }
    }
  }
]

When #

# HTTP POST /hooks/signed
# HTTP POST /hooks/signed

Then #

  • after HTTP POST /hooks/signed:
    • HTTP status is 500
    • body contains Error occurred while evaluating hook rules
    • file ran.txt does not exist
  • after HTTP POST /hooks/signed:
    • HTTP status is 200
    • file ran.txt contains signed

Scenario: the http-methods allowlist rejects the wrong verb #

Given #

  • Background service webhook is started: webhook -hooks hooks.json -ip 127.0.0.1 -port 18097.
  • Fixture file handler.sh is created.
  • Fixture file hooks.json is created.

Inputs #

Fixture handler.sh:

#!/bin/sh
printf 'ok'

Fixture hooks.json:

[
  {
    "id": "postonly",
    "execute-command": "${workdir}/handler.sh",
    "command-working-directory": "${workdir}",
    "http-methods": ["POST"],
    "include-command-output-in-response": true
  }
]

When #

# HTTP GET /hooks/postonly

Then #

  • HTTP status is 405

Scenario: a command that exits non-zero surfaces as a 500 #

Given #

  • Background service webhook is started: webhook -hooks hooks.json -ip 127.0.0.1 -port 18098.
  • Fixture file handler.sh is created.
  • Fixture file hooks.json is created.

Inputs #

Fixture handler.sh:

#!/bin/sh
printf 'boom\n' >&2
exit 3

Fixture hooks.json:

[
  {
    "id": "failer",
    "execute-command": "${workdir}/handler.sh",
    "command-working-directory": "${workdir}",
    "include-command-output-in-response": true
  }
]

When #

# HTTP POST /hooks/failer

Then #

  • HTTP status is 500
  • body contains Error occurred while executing the hook's command