{"id":18482859,"url":"https://github.com/spaze/security-txt","last_synced_at":"2026-04-06T02:03:31.610Z","repository":{"id":57056559,"uuid":"447710753","full_name":"spaze/security-txt","owner":"spaze","description":"security.txt (RFC 9116) generator, parser, validator","archived":false,"fork":false,"pushed_at":"2025-09-25T23:33:45.000Z","size":427,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-26T01:24:18.556Z","etag":null,"topics":["generator","parser","security","security-txt","securitytxt","validator"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spaze.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-01-13T18:34:15.000Z","updated_at":"2025-09-25T23:33:49.000Z","dependencies_parsed_at":"2024-08-09T12:26:42.911Z","dependency_job_id":"3de5a426-6e46-4f67-a091-b39c0dcd0d9a","html_url":"https://github.com/spaze/security-txt","commit_stats":{"total_commits":31,"total_committers":1,"mean_commits":31.0,"dds":0.0,"last_synced_commit":"38c0b071e9b7174fd422872ab92d433177ba3ee4"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/spaze/security-txt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spaze%2Fsecurity-txt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spaze%2Fsecurity-txt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spaze%2Fsecurity-txt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spaze%2Fsecurity-txt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spaze","download_url":"https://codeload.github.com/spaze/security-txt/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spaze%2Fsecurity-txt/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279007342,"owners_count":26084282,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-11T02:00:06.511Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["generator","parser","security","security-txt","securitytxt","validator"],"created_at":"2024-11-06T12:31:21.097Z","updated_at":"2026-04-06T02:03:31.603Z","avatar_url":"https://github.com/spaze.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `security.txt` (RFC 9116) generator, parser, validator\n\nThis package is a PHP library that can generate, parse, and validate `security.txt` files. It comes with an executable script that you can use from the command line, in a CI test, or in a pipeline (for example, in GitHub Actions).\n\nThe `security.txt` document represents a text file that's both human-readable and machine-parsable to help organizations describe their vulnerability disclosure  practices to make it easier for researchers to report vulnerabilities.\nThe format was created by EdOverflow and Yakov Shafranovich and is specified in [RFC 9116](https://www.rfc-editor.org/rfc/rfc9116).\nYou can find more about \u003ccode\u003esecurity.txt\u003c/code\u003e at \u003ca href=\"https://securitytxt.org/\"\u003esecuritytxt.org\u003c/a\u003e.\n\nI have also written a blogpost about `security.txt` and how it may be helpful when reporting vulnerabilities:\n- [What's `security.txt` and why you should have one](https://www.michalspacek.com/what-is-security.txt-and-why-you-should-have-one) in English\n- [K čemu je soubor `security.txt`](https://www.michalspacek.cz/k-cemu-je-soubor-security.txt) in Czech\n\n# Installation\n\nInstall the package with Composer:\n```\ncomposer require spaze/security-txt\n```\n\n# Requirements and supported versions\n\n| Version | Requirements                                                                                                                   | Notes                  |\n|---------|--------------------------------------------------------------------------------------------------------------------------------|------------------------|\n| 1.x     | PHP 8.3, 8.4, 8.5\u003cbr/\u003e+ optional curl extension to fetch from remote hosts\u003cbr/\u003e+ optional gnupg extension to verify signatures | Current stable release |\n\n# As a validator\n\n## How does the validation work\nThis package can validate `security.txt` file either by providing\n- the file contents as a string by calling `Spaze\\SecurityTxt\\Parser\\SecurityTxtParser::parseString()`\n- a hostname like `example.com` to `Spaze\\SecurityTxt\\Parser\\SecurityTxtParser::parseHost()`\n- a URL like `https://example.com/` to `Spaze\\SecurityTxt\\Check\\SecurityTxtCheckHost::check()`\n\nEach of the options above will call preceding method and add more validations which are only possible in that particular case.\n\nThere's also a command line script in `bin` which uses `Spaze\\SecurityTxt\\Check\\SecurityTxtCheckHostCli::check()` mostly just to add command line output to `Spaze\\SecurityTxt\\Check\\SecurityTxtCheckHost::check()`.\n\nIf you want to decouple fetching the `security.txt` file and parsing it, there's also a possibility to pass a `SecurityTxtFetchResult` object to `Spaze\\SecurityTxt\\Parser\\SecurityTxtParser::parseFetchResult()`.\n\n## How to use the validator\n`Spaze\\SecurityTxt\\Check\\SecurityTxtCheckHost::check()` is probably what you'd want to use as it provides the most comprehensive checks, can pass a URL, not just a hostname, and also supports callbacks. It accepts these parameters:\n\n`string $url`\n\nA URL where the file will be looked for, you can pass just for example `https://example.com`, no need to use the full path to the `security.txt` file, because only the hostname of the URL will be used for further checks\n\n`?int $expiresWarningThreshold = null`\n\nThe validator will start throwing warnings if the file expires soon, and you can say what \"soon\" means by specifying the number of days here\n\n`bool $strictMode = false`\n\nIf you enable strict mode, then the file will be considered invalid, meaning `SecurityTxtCheckHostResult::isValid()` will return `false` even when there are only warnings, with strict mode disabled, the file with only warnings would still be valid and `SecurityTxtCheckHostResult::isValid()` would return `true`\n\n`bool $requireTopLevelLocation = false`\n\nWhen specified, the top-level `/security.txt` location must also exist (or be redirected) in addition to `/.well-known/security.txt`, otherwise a warning will be issued\n\n`bool $noIpv6 = false`\n\nBecause some environments do not support IPv6, looking at you GitHub Actions\n\n`?int $maxAllowedRedirects = null`\n\nMaximum number of redirects to follow when fetching `security.txt`. Set to `0` to disable redirects, `null` to use the default (`5`).\n\n`Spaze\\SecurityTxt\\Check\\SecurityTxtCheckHost::check()` returns a `Spaze\\SecurityTxt\\Check\\SecurityTxtCheckHostResult` object with some obvious and less obvious properties.\nThe less obvious ones can be obtained with the following methods. All of them return an array of `SecurityTxtSpecViolation` descendants.\n\n### `getFetchErrors()`\nReturns `list\u003cSecurityTxtSpecViolation\u003e` and contains errors encountered when fetching the file from a server. For example but not limited to:\n- When the content type or charset is wrong\n- When the URL scheme is not HTTPS\n\n### `getFetchWarnings()`\nAlso returns `list\u003cSecurityTxtSpecViolation\u003e` and has warnings when fetching the file, like for example but not limited to:\n- When the files at `/security.txt` and `/.well-known/security.txt` differ\n- When `/security.txt` does not redirect to `/.well-known/security.txt`\n\n### `getLineErrors()`\nReturns `array\u003cint, list\u003cSecurityTxtSpecViolation\u003e\u003e` where the array `int` key is the line number. Contains errors discovered when parsing and validating the contents of the `security.txt` file. These errors are produced by any class that implements the `FieldProcessor` interface. The errors include but are not limited to:\n- When a field uses incorrect separators\n- When a field value is not URL or the URL doesn't use `https://` scheme\n\n### `getLineWarnings()`\nAlso returns `array\u003cint, list\u003cSecurityTxtSpecViolation\u003e\u003e` where the array `int` key is the line number. Contains warnings generated by any class that implements the `FieldProcessor` interface, when parsing and validating the contents of the `security.txt` file. These warnings include but are not limited to:\n- When the `Expires` field's value is too far into the future\n\n### `getFileErrors()`\nReturns `list\u003cSecurityTxtSpecViolation\u003e`, the list contains file-level errors which cannot be paired with any single line. These error are generated by `FieldValidator` child classes, and include:\n- When mandatory fields like `Contact` or `Expires` are missing\n\n### `getFileWarnings()`\nReturns `list\u003cSecurityTxtSpecViolation\u003e`, the list contains file-level warnings that cannot be paired with any single line. These warnings are generated by `FieldValidator` child classes, and include for example:\n- When the file is signed, but there's no `Canonical` field\n\n## Callbacks\n`SecurityTxtCheckHost::check()` supports callbacks that can be set with `SecurityTxtCheckHost::addOn*()` methods. You can use them to get the parsing information in \"real time\", and are used for example by the `bin/checksecuritytxt.php` script via the `\\Spaze\\SecurityTxt\\Check\\SecurityTxtCheckHostCli` class to print information as soon as it is available.\n\n## JSON\nThe `Spaze\\SecurityTxt\\Check\\SecurityTxtCheckHostResult` object can be encoded to JSON with `json_encode()`,\nand decoded back with `Spaze\\SecurityTxt\\Json\\SecurityTxtJson::createCheckHostResultFromJsonValues()`.\n\nThe primary use case for JSON-encoded objects is a result cache. But JSON can also be used when you want to fetch `security.txt` using serverless services like AWS Lambda,\nand then process the fetch result yourself.\n\nIf that's the case, then you may want to encode the `Spaze\\SecurityTxt\\Fetcher\\SecurityTxtFetchResult` object created by `Spaze\\SecurityTxt\\Fetcher\\SecurityTxtFetcher::fetch()`.\n`Spaze\\SecurityTxt\\Json\\SecurityTxtJson::createFetchResultFromJsonValues()` then decodes it back from JSON.\n\nFetch exceptions can be recreated with `Spaze\\SecurityTxt\\Json\\SecurityTxtJson::createFetcherExceptionFromJsonValues()`.\n\nJSON is not versioned. Newer versions of this library will make a best effort to decode JSON created by previous versions, but compatibility cannot be guaranteed across refactors or format changes.\n\n## The other methods\nBoth `Spaze\\SecurityTxt\\Parser\\SecurityTxtParser::parseString()` and `Spaze\\SecurityTxt\\Parser\\SecurityTxtParser::parseHost()` return a `Spaze\\SecurityTxt\\Parser\\SecurityTxtParseResult` object with similar methods as what's described above for `SecurityTxtCheckHostResult`.\nThe result returned from `parseHost()` also contains `Spaze\\SecurityTxt\\Fetcher\\SecurityTxtFetchResult` object.\n\n# As a writer\nYou can create a `security.txt` file programmatically:\n1. Create a `SecurityTxt` object\n2. Add what's needed\n3. Pass it to `SecurityTxtWriter::write()` it will return the `security.txt` contents as a string\n\nSee below if you want to add an OpenPGP signature.\n\n## Value validation\nBy default, values are validated when set, and an exception is thrown when they're invalid. You can set validation level in the `SecurityTxt` constructor using the `SecurityTxtValidationLevel` enum:\n- `NoInvalidValues` (an exception will be thrown, and the value won't be set, this is the default setting)\n- `AllowInvalidValues` (an exception will be thrown but the value will still be set)\n- `AllowInvalidValuesSilently` (an exception will not be thrown, and the value will be set)\n\n## Content type\nYou can use the following `SecurityTxtContentType` constants to serve the file with correct HTTP content type:\n- `SecurityTxtContentType::MEDIA_TYPE`, the value to be sent as `Content-Type` header value (`text/plain; charset=utf-8`);\n- `SecurityTxtContentType::CONTENT_TYPE`, the correct content type `text/plain`\n- `SecurityTxtContentType::CHARSET`, the charset `utf-8`\n- `SecurityTxtContentType::CHARSET_PARAMETER`, the correct charset parameter name and value as `charset=utf-8`\n\n## Example\n```php\n$securityTxt = new SecurityTxt();\n$securityTxt-\u003eaddContact(new SecurityTxtContact('https://contact.example'));\n$securityTxt-\u003eaddContact(SecurityTxtContact::phone('123456'));\n$securityTxt-\u003eaddContact(SecurityTxtContact::email('email@com.example'));\n$securityTxt-\u003eaddAcknowledgments(new SecurityTxtAcknowledgments('https://ack1.example'));\n$securityTxt-\u003esetExpires(new SecurityTxtExpiresFactory()-\u003ecreate(new DateTimeImmutable('+3 months midnight')));\n$securityTxt-\u003eaddAcknowledgments(new SecurityTxtAcknowledgments('ftp://ack2.example'));\n$securityTxt-\u003esetPreferredLanguages(new SecurityTxtPreferredLanguages(['en', 'cs-CZ']));\nheader('Content-Type: ' . SecurityTxtContentType::MEDIA_TYPE);\necho new SecurityTxtWriter()-\u003ewrite($securityTxt);\n```\n\n## Signing the file\nOne option to sign the file using an OpenPGP cleartext signature as per the `security.txt` [specification](https://www.rfc-editor.org/rfc/rfc9116#name-digital-signature) is to pre-sign the `security.txt` file using the `gpg` command line utility and store the result as a static file in your repository.\nI'd recommend creating the signatures that way as it doesn't expose your private keys to the web server and the web app. Allowing the app and the server to access your private keys brings a handful of new security problems to solve, which some of them are mentioned below.\n\nCreating a new signing key is beyond the scope of this document, but you can refer to sources like [the GitHub Docs](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key).\nRelated challenges like key distribution, secure storage, and expiration, while interesting to address properly, are also not covered here.\n\nHaving said that, this library also allows you to create the signature programmatically by calling `Spaze\\SecurityTxt\\Signature\\SecurityTxtSignature::sign()` (requires the `gnupg` PHP extension):\n```php\n$gnuPgProvider = new SecurityTxtSignatureGnuPgProvider();\n$signature = new SecurityTxtSignature($gnuPgProvider);\n$securityTxt = new SecurityTxt();\n// $securityTxt-\u003eaddContact(...) etc.\n$writer = new SecurityTxtWriter();\n$contents = $writer-\u003ewrite($securityTxt);\n$signingKeyFingerprint = '...'; // Or anything that refers to a unique key (user id, key id, ...)\n$keyPassphrase = '...'; // Don't commit the passphrase to Git, please don't\necho $signature-\u003esign($contents, $signingKeyFingerprint, $keyPassphrase);\n```\n\nThe `SecurityTxtSignature::sign()` method makes use of the keyring of the current user (which may be a web server user).\nThis keyring is normally located in the `.gnupg` directory in the user's home dir. To specify a custom location,\npass the path to the keyring in the `Spaze\\SecurityTxt\\Signature\\Providers\\SecurityTxtSignatureGnuPgProvider` constructor, for example:\n```php\n$gnuPgProvider = new SecurityTxtSignatureGnuPgProvider('/home/www');\n```\nIf you wish, you can instead store the path to the keyring in the environment variable `GNUPGHOME`.\nMake sure the keyring is not publicly accessible, do not store keyring in `public_html` or similar directories. Also don't add the keyring to your Git repository.\n\nIf you're going to use a key for this library, I'd strongly recommend you create a key only to sign the file and do not use the key for anything else. You can then sign the key with your main key, if you want.\n\n### Caching the signed file\nIf you're going to create the signature using this library, I don't recommend doing it on each request. Instead, you can cache the signed contents using for example the [Symfony Cache](https://symfony.com/doc/current/components/cache.html) component:\n```php\nuse Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter;\nuse Symfony\\Contracts\\Cache\\ItemInterface;\n\n$cache = new FilesystemAdapter();\n$cachedContents = $cache-\u003eget('securitytxt_file', function (ItemInterface $item) use ($securityTxt, $signature, $contents, $signingKeyFingerprint, $keyPassphrase): string {\n    $item-\u003eexpiresAt($securityTxt-\u003egetExpires()-\u003egetDateTime());\n    return $signature-\u003esign($contents, $signingKeyFingerprint, $keyPassphrase);\n});\n\necho $cachedContents;\n```\nThe following example uses the [Nette Cache](https://doc.nette.org/en/caching) library, the code is very similar to the example above:\n```php\nuse Nette\\Caching\\Cache;\nuse Nette\\Caching\\Storages\\FileStorage;\n\n$storage = new FileStorage('/tmp/cache');\n$cache = new Cache($storage);\n$cachedContents = $cache-\u003eload('securitytxt_file', function () use ($signature, $contents, $signingKeyFingerprint, $keyPassphrase): string {\n    return $signature-\u003esign($contents, $signingKeyFingerprint, $keyPassphrase);\n}, [Cache::Expire =\u003e $securityTxt-\u003egetExpires()-\u003egetDateTime()]);\n\necho $cachedContents;\n```\n\n# Command line usage\nThe `checksecuritytxt.php` script, located in the `bin` directory, prints progress and validation errors and warnings. It can be used from the command line or in automated tests.\n\nUsage:\n```sh\nchecksecuritytxt.php \u003cURL or hostname\u003e [days] [--colors] [--strict] [--require-top-level-location] [--no-ipv6]\n```\n\nParameters:\n- `URL or hostname`: A hostname or a domain you want to check (e.g. `example.com`). If you provide a URL (e.g., `https://example.com/foo`), the script will extract and use only the hostname part anyway.\n- `days`: If the file expires in less than *`days`* days, the script will print a warning.\n- `--colors`: Enables colored output using red, green, and other colors for better readability.\n- `--strict`: Upgrades all warnings to errors, enforcing stricter validation.\n- `--require-top-level-location`: When specified, the `/security.txt` location must also exist or be redirected, otherwise a warning will be issued.\n- `--no-ipv6`: Disables IPv6 usage. When this option is set, the script effectively ignores AAAA DNS records and uses only A records.\n\nThe script returns the following status codes:\n- `0`: The file is valid.\n- `1`: Returned if any of the following conditions are true:\n  - The file has expired.\n  - The file has errors.\n  - The file has warnings when using `--strict`.\n- `2`: No hostname or URL was passed.\n- `3`: The file cannot be loaded.\n\n# CI Pipelines\nIf you'd like to check your `security.txt` file automatically using a CI (continuous integration) platform, such as GitHub Actions, you can use the command-line script described above.\nIn general, you’ll need to follow these steps:\n\n1. Install PHP if it is not already installed.\n2. Install this package using Composer.\n3. Run the `checksecuritytxt.php` script.\n\nGitHub Actions' `ubuntu-24.04` runner (also as `ubuntu-latest` at the time of writing) has PHP 8.3 preinstalled, so you can use `checksecuritytxt.php` without installing anything else, this lib can be used with PHP 8.3.\nBut unfortunately the `gnupg` PHP extension is not available on GitHub runners so you won't be able to verify the file signatures.\nIf you want to verify signatures you'll need to use [the `setup-php` GitHub action](https://github.com/marketplace/actions/setup-php-action) which can also set up the extension.\n\nYou can use my own checks as a template or for inspiration; see [the `securitytxt.yml` file](https://github.com/spaze/michalspacek.cz/blob/main/.github/workflows/securitytxt.yml) in my repository.\n\n# Exceptions\nThe messages in the exceptions as thrown by this library do not contain any sensitive information and are safe to display to the user using the `getMessage()` method.\nBut please be aware that the messages contain server-supplied information, so please do not display the messages as HTML or do not feed them into a Markdown parser or similar.\nIf you'd do that, a malicious server could inject content that would result in Cross-Site Scripting attack for example.\n\n## Formatting messages\nIf you'd like to format some of the values contained in the messages, you can use the exception's `getMessageFormat()` and `getMessageValues()` methods.\nThe `getMessageFormat()` method will return an error message with `%s` placeholders, while `getMessageValues()` will return the values, including the server-supplied ones,\nwhich you can, **after a proper sanitization and/or escaping**, wrap in `\u003ccode\u003e` tags for example, and use them to replace the placeholders.\n\nThe same goes for formatting `SecurityTxtSpecViolation` object messages: you can use `getMessageFormat()` and `getMessageValues()`, and also `getHowToFixFormat()` and `getHowToFixValues()`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspaze%2Fsecurity-txt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspaze%2Fsecurity-txt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspaze%2Fsecurity-txt/lists"}