App Signals
sending new scorecard signals from your app
An app can provide a new type of security signals that surfaces in company scorecards.
This can be added to your app manifest:
{
...
"signals": [
{
// unique identifier for this signal (must be snake_case)
// note this must be prefixed by your app unique namespace (separated by a dot)
"id": "your_app.your_signal",
// human-readable description of this signal
"name": "Signal Name",
// only "info" or "positive" are allowed here
"severity": "positive",
// one of https://api.securityscorecard.io/metadata/factors
"factor": "patching_cadence",
"short_description": "risk or short description of this signal",
"long_description": "long description of this signal",
"recommendation": "how to remediate (if negative) or obtain (if positive) this signal",
// a username (ideally a bot) that is authorized to send these signals
// (see below "Sending Signals")
"sent_by": "<a username>",
// links with more information
"references": [{
"link": "<url of an article with more information>",
"text": "human-readable title for this link"
}, {
"link": "<url of another article with more information>",
"text": "human-readable title for this link"
}]
}
]
}
Signal Id and Namespace
The signal id
must be unique and will be visible in urls when showing these signals in a scorecard. it's snake_case
, but notice it must always be prefixed by a unique namespace of your app (the can also be snake_case
if it has multiple words), the namespace is separated by a dot.
Sending Signals
Signals can be send in bulk using this endpoint:
curl -X PATCH \
https://api.securityscorecard.io/signals/by-type/your_app.your_signal \
-H 'content-type: application/json'
-H 'authorization: Token <your API key>'
--data '[{"op":"add","value": { ... } }]'
The list of signals in the request body here is limited to 10000 items, and follows the structure of a JSONPatch.
Authorization
Signals can only be sent by either the creator of of the app (typically the first user to install), or by authorizing a specific username in your manifest. Adding a username under sent_by
authorizes that user to send that type of signal.
{
...
"signals": [
{
...
// a username (ideally a bot) that is authorized to send these signals
"sent_by": "1deba750-705b-11eb-b6c3-6928ee4eb60e",
...
}
]
}
Beyond a testing phase, It's highly recommend you use a Bot user, The username of a bot is a UUID shown in your User list under the bot's name.
New signals
{
"op": "add",
"value":{
// human-readable description of what was observed
"summary": "a summary",
// optional, an external url with more details or evidence (it can be relative to app manifest)
"url": "/signals/123",
// the internet domain associated to this signal, any valid internet domain
// this will be used to determine an associated company scorecard
"domain": "www.example.com",
// optional, when applicable used to indicate the IP (or IP range) where this was observed
"ip": "4.4.4.4",
// optional, used to indicate the number of times this signal was observed (might apply depending on the signal type)
"count": 123,
// optional, can be specified to apply arbritrary labels
"labels": ["windows"],
// when this signal was first observed (optional, defaults to now)
"first_seen": "iso timestamp",
// when this signal was last observed (optional, defaults to now)
"last_seen": "iso timestamp"
}
}
Duplicates will aggregate count
, or used only to update last_seen
to NOW()
and to re-activate a signal that was decayed
("add" always set status to active
)
Updating existing signals
Signals should be treated as immutable, preserved as as part of scorecard history, but you can update their last_seen
(indicating the signal is still present), or they can be marked as decayed
or resolved
, making them effectively disappear from a scorecard list of active signals.
You could increase the count
by making an op: add over an existing signal (see New Signals).
Signals can be updated by sending a an exact duplicate with new values for either last_seen
or status
as described below, or by sending a replace
operation:
{
"op": "replace",
"path": "/{signal id}",
"value": {
// optional, it can be used to update when the signal was last observed
"last_seen": "iso timestamp",
// optional, used to indicate this signal is not observed anymore,
// in which case it will be removed from scorecards
"status": "decayed"
}
}
One caveat with replace
operations is they require the path
attribute (containing /{signal id}
) to identify which signal is getting the update.
The only change allowed to update signals without passing the path
is setting decayed
as the status. For that, you only need to pass domain
so the signal(s) can be updated (1 or more). Meaning, you would use this method for decaying all signal entries for a signal.type
and the subject domain
:
{
"op": "replace",
"value": {
// the internet domain associated to this signal, any valid internet domain
// this will be used to determine an associated company scorecard
"domain": "www.example.com",
// optional, it can be used to update when the signal was last observed
"last_seen": "iso timestamp",
"status": "decayed"
}
}
Notice that updating status to "decayed"
(signal is not observed anymore) or "resolved"
(an action was token fix the associated issue) is the only mechanism to remove existing signals from scorecards, as signals are always preserved for historical context.
Updated about 1 year ago