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 Network policy: egress is allowed only to 127.0.0.1.

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

only when webhook -version succeeds

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 via hooks_a
# HTTP POST /hooks/nope via hooks_a

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 #

only when webhook -version succeeds

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 via hooks_b
# HTTP POST /hooks/guarded via hooks_b

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 #

only when webhook -version succeeds

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 via hooks_c
# HTTP POST /hooks/signed via hooks_c

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 #

only when webhook -version succeeds

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 via hooks_d

Then #

  • HTTP status is 405

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

only when webhook -version succeeds

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 via hooks_e

Then #

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