Applied Practice Experience in Cybersecurity

Chapter 9
Secure Engineering, Cloud and DevSecOps Practice

Post Graduate Program in Cyber Security
Applied Practice Experience in Cybersecurity   •   Post Graduate

9. Secure Engineering, Cloud and DevSecOps Practice

Introduction and Context

Modern cybersecurity is inseparable from the way digital services are designed, developed, deployed, and operated. Security cannot be treated as a final inspection performed immediately before release. Applications are assembled from open-source libraries, cloud services, containers, infrastructure code, APIs, identity systems, and automated delivery pipelines. A weakness introduced in any one of these elements may become exploitable when the system reaches production.

Secure engineering therefore integrates security decisions into the complete technology lifecycle. It begins with understanding business objectives and threats, continues through architecture and coding, and extends into testing, deployment, monitoring, incident response, and retirement. DevSecOps applies this principle to environments in which development, security, and operations collaborate through automation and shared responsibility. Cloud computing intensifies the need for this integration because infrastructure is programmable, highly distributed, and governed by a shared-responsibility model.

This chapter examines how threat modeling, secure design, software and dependency analysis, CI/CD controls, infrastructure-as-code security, container protection, and cloud governance operate as one delivery practice. The objective is not simply to list tools, but to develop the professional judgment required to select appropriate controls, interpret findings, manage risk, and preserve delivery speed without weakening assurance.

Learning Objectives

By the end of this chapter, a learner should be able to:

1. Explain the relationship between secure engineering, DevSecOps, cloud security, and software supply-chain risk.

2. Construct a practical threat model using assets, trust boundaries, threats, vulnerabilities, and mitigations.

3. Distinguish among SAST, DAST, SCA, secrets detection, software composition analysis, and runtime testing.

4. Design security controls for CI/CD pipelines and infrastructure-as-code.

5. Assess container images, orchestration configurations, and cloud services using shared-responsibility principles.

6. Evaluate security findings according to exploitability, impact, exposure, and business context.

7. Recommend governance, ethical, and operational practices for secure delivery.

From Secure Development to DevSecOps

Secure software development traditionally placed security reviews at selected project gates. This approach may identify serious defects, but late discovery is expensive: architectural weaknesses may require redesign, and developers may regard security as an external obstacle. Secure engineering shifts security decisions toward the earliest feasible point while retaining verification throughout the lifecycle.

DevSecOps is a sociotechnical practice rather than a software product. It combines collaborative culture, automated controls, measurable risk decisions, and operational feedback. Development teams remain accountable for the security of the code they create; security specialists provide expertise, standards, threat analysis, and assurance; operations teams protect runtime environments and report real-world signals. Product owners and senior managers decide risk appetite, release priorities, and acceptable residual risk.

The expression “shift left” refers to moving suitable security activities earlier in development. It does not mean shifting every responsibility to developers or eliminating runtime controls. Some risks can only be evaluated in a deployed environment, particularly configuration drift, identity abuse, anomalous behavior, and attacks against live dependencies. A mature program therefore uses both shift-left and shift-right practices.

Important related terms include:

  • Secure-by-design: security requirements and architectural safeguards are established before implementation.
  • Security as code: policies, tests, configuration rules, and compliance checks are represented in version-controlled, executable form.
  • Policy as code: machine-enforceable rules define conditions under which infrastructure or releases are permitted.
  • Continuous assurance: security evidence is collected repeatedly across design, build, deployment, and operation.
  • Software supply chain: the people, processes, tools, source code, dependencies, build systems, artifacts, and distribution mechanisms used to produce software.

Threat Modeling as an Engineering Activity

Threat modeling is a structured method for anticipating how a system might be misused or attacked. It is not a prediction of every possible attack. Its value lies in making assumptions explicit, identifying important attack paths, and connecting threats to design decisions and testable requirements.

A useful threat-modeling process begins by defining scope. The team records the system’s purpose, users, external services, data types, deployment environments, and security objectives. It then creates a data-flow or architecture diagram showing processes, data stores, data flows, external entities, and trust boundaries. A trust boundary is a point at which data or authority moves between components governed by different assumptions—for example, from a public client to an API, or from an application account to a managed cloud service.

The team next identifies assets and security properties. Assets may include credentials, personal information, payment records, intellectual property, availability, or the integrity of a transaction. Confidentiality, integrity, availability, authenticity, accountability, and privacy provide a vocabulary for describing protection needs.

Threats can be organized through approaches such as STRIDE: spoofing, tampering, repudiation, information disclosure, denial of service, and elevation of privilege. Attack trees, abuse cases, and misuse cases offer complementary methods. A threat should be expressed concretely: “An unauthenticated internet user can alter an order by modifying an identifier in the request” is more useful than “the application may be attacked.”

Risk analysis then considers likelihood, impact, exposure, existing controls, and uncertainty. Quantitative precision is not always possible, but the reasoning must be transparent. High-impact and easily exploitable threats usually require design changes, while low-risk findings may be accepted, transferred, monitored, or scheduled. Each important threat should produce a mitigation, an owner, a verification method, and—where relevant—a residual-risk decision.

Threat models must be maintained. New integrations, changes in authentication, migration to cloud services, and introduction of a privileged pipeline should trigger review. A stale diagram may provide false assurance and conceal changed trust relationships.

Secure Design and Architecture

Secure design converts threat analysis into system properties. Authentication verifies identity; authorization determines permitted actions; accounting records relevant activity. These functions should not be confused. A valid login does not justify unrestricted access, and a log entry is not a substitute for prevention.

Least privilege limits each identity, service, workload, and pipeline to the permissions required for its task. Separation of duties reduces the risk that one person or automated process can develop, approve, and deploy a sensitive change without independent review. Defense in depth uses multiple safeguards so that failure of one control does not expose the entire system.

Secure architecture commonly includes strong identity boundaries, explicit authorization checks, encrypted communications, protected secrets, input validation, safe error handling, rate limiting, resilient dependencies, and centralized audit logging. Sensitive data should be classified and retained only as long as justified. Encryption is valuable, but it does not compensate for excessive permissions, weak key management, or insecure application logic.

Designers should prefer secure defaults. A new service should deny access until explicitly authorized, avoid public network exposure unless required, and use managed identity or short-lived credentials rather than embedded keys. Security requirements should be testable, such as “only the order service may write to the order database” or “administrative actions require phishing-resistant multifactor authentication.”

Code, Dependency, and Secret Analysis

Security testing is most effective when each technique is understood in context. Static application security testing (SAST) examines source code or intermediate representations without executing the application. It can identify unsafe data flows, injection risks, and insecure API use early, but it may produce false positives and cannot fully understand runtime configuration.

Dynamic application security testing (DAST) interacts with a running application. It can reveal externally observable weaknesses, authentication problems, and deployment issues, but coverage depends on reachable functionality and test quality. Interactive application security testing combines runtime observation with code context. Manual review and penetration testing remain important for business-logic flaws that automated tools often miss.

Software composition analysis (SCA) identifies third-party packages, versions, licenses, and known vulnerabilities. A vulnerability identifier alone does not establish practical risk. Analysts should consider whether the affected component is present in the deployed artifact, whether the vulnerable function is reachable, whether exploitation is public, and whether compensating controls exist. Dependency pinning, lockfiles, trusted registries, update automation, and review of transitive dependencies reduce supply-chain uncertainty.

Secret scanning detects credentials, tokens, private keys, and other sensitive values in repositories or build outputs. A discovered secret must be treated as compromised: remove it from use, rotate it, investigate access, and prevent recurrence. Merely deleting the value from the latest commit is inadequate if it remains in repository history or build caches.

A software bill of materials (SBOM) records components included in an artifact. It supports vulnerability response, procurement assurance, licensing review, and incident investigation. SBOM quality depends on completeness, accurate version identification, and linkage to the actual artifact deployed.

Securing CI/CD Pipelines

A CI/CD pipeline is a high-value target because it can transform source code into trusted production software. Pipeline security begins with protecting source repositories, branch policies, code review, build agents, artifact repositories, deployment credentials, and approval mechanisms.

A defensible pipeline generally performs the following activities:

  • validates commits and branch protections;
  • scans source code, dependencies, secrets, and generated artifacts;
  • builds in controlled and preferably ephemeral environments;
  • signs or otherwise attests to build provenance;
  • stores artifacts in access-controlled repositories;
  • deploys immutable, tested artifacts rather than rebuilding during release;
  • applies environment-specific approvals and policy checks; and
  • records evidence sufficient to reconstruct who changed, approved, built, and deployed a release.

Pipeline permissions should be narrowly scoped. A build job that compiles code should not automatically possess production database credentials. Secrets should be injected through an approved secret-management service, not placed in scripts, environment files, or command-line arguments that may be logged. Third-party pipeline actions and plugins require version control, provenance review, and permissions analysis because they execute with pipeline authority.

Security gates must be risk-based. Blocking every theoretical finding encourages bypasses and alert fatigue. A practical policy may block releases for confirmed critical vulnerabilities in reachable production code, exposed secrets, unsigned artifacts, or prohibited infrastructure configurations, while routing lower-confidence findings for triage. Exceptions should be documented, time-limited, owned, and reviewed.

Infrastructure as Code and Configuration Governance

Infrastructure as code (IaC) defines networks, identities, storage, compute, policies, and services in declarative or programmable files. IaC improves repeatability and reviewability, but an insecure template can reproduce weakness at scale.

IaC security includes static scanning of templates, validation of variables and modules, secure state management, policy enforcement, and post-deployment verification. Common risks include publicly readable storage, unrestricted security-group rules, excessive identity permissions, unencrypted databases, disabled logging, and hard-coded secrets. State files may contain sensitive values and must be encrypted, access-controlled, versioned appropriately, and protected against unauthorized modification.

Reusable modules improve consistency only when they are governed. Organizations should maintain approved modules, specify versions, review changes, and test modules in isolated environments. Policy-as-code tools can reject prohibited configurations before deployment—for example, a storage resource lacking encryption or an identity policy granting unrestricted administrative access. However, policy rules should be tested to avoid both dangerous gaps and excessive restrictions that teams routinely circumvent.

Drift detection compares declared configuration with deployed reality. Drift may result from emergency changes, compromised credentials, provider behavior, or undocumented operational work. The response should distinguish legitimate temporary action from unauthorized change and restore the declared state only after understanding consequences.

Containers and Orchestrated Workloads

Containers package an application and its dependencies, but they do not automatically provide isolation equivalent to a virtual machine. Security begins with trusted minimal base images, timely patching, non-root execution, removal of unnecessary packages, and prevention of embedded secrets. Images should be scanned before release and continuously as vulnerability information changes.

Image provenance is important. Organizations should know how an image was built, from which source, with what dependencies, and whether it was altered. Signing and verification can help ensure that only approved images enter production. Registries require access control, retention policies, malware scanning where appropriate, and protection against unauthorized replacement.

In an orchestrator such as Kubernetes, security extends beyond the image. Workload identity, admission policies, network segmentation, resource limits, encrypted control-plane communication, audit logs, and restricted service-account permissions are essential. A pod that runs as root, mounts the host filesystem, or possesses unnecessary capabilities may undermine the host or neighboring workloads. Secrets should use appropriate secret-management mechanisms and should not be treated as harmless configuration.

Runtime monitoring detects unexpected processes, network connections, privilege changes, and unusual resource consumption. Container security is therefore a lifecycle concern: build, registry, deployment, configuration, runtime, and retirement must be addressed together.

Cloud Security and Shared Responsibility

Cloud security responsibility is shared, but it is not necessarily equal. The provider generally secures the underlying physical facilities, hardware, and core service infrastructure. The customer remains responsible for appropriate identity management, data protection, configurations, workloads, and compliance obligations. The exact division varies by service model and provider.

In infrastructure-as-a-service, customers usually control operating systems, applications, network rules, and data. In platform-as-a-service, the provider manages more of the operating environment, while customers still control application logic, identities, data, and service configuration. In software-as-a-service, the provider manages the application platform, but customers remain responsible for users, access rights, data governance, and appropriate use.

Cloud misconfigurations frequently arise from excessive permissions, public exposure, weak logging, insecure storage, unmanaged keys, and poor segmentation. A cloud security program should establish account or subscription structure, identity federation, multifactor authentication, centralized logging, baseline configurations, key-management processes, backup protection, vulnerability management, and incident-response procedures. Security posture management tools can identify deviations, but findings still require contextual analysis and ownership.

Cloud-native architecture also changes risk. Elasticity can amplify denial-of-service costs; managed services can create dependency and concentration risk; APIs and identity policies become central control points; and ephemeral resources complicate evidence collection. Governance must therefore address both technical controls and financial or operational guardrails.

Illustrative Case: Meridian Health Analytics

Background

Meridian Health Analytics, a fictional provider of clinical reporting software, decided to migrate its reporting platform to a managed cloud environment. The application used containerized services, open-source libraries, infrastructure templates, and an automated deployment pipeline. The business required rapid release of new analytics features for hospitals.

Situation

A pre-release review identified a publicly accessible object-storage bucket, a container image running as root, a transitive library with a serious known vulnerability, and a pipeline token with permissions across several production accounts. Developers argued that remediation would delay an important release. The security team initially proposed blocking all deployments until every scanner finding was resolved.

Analysis

Threat modeling showed that the bucket contained exported reports and that public access would create a high-impact confidentiality threat. The vulnerable library was present in the image, but analysis showed that the affected function was reachable through an exposed reporting endpoint. The root container increased the impact of a possible application compromise. The pipeline token created a high-consequence supply-chain path because compromise of a build job could affect multiple environments.

The team separated findings by exploitability, impact, exposure, and confidence. The bucket and pipeline token required immediate action. The reachable dependency and container privilege issue required remediation before release. Several low-confidence scanner alerts were recorded for later investigation rather than used as automatic blockers.

Approach and Solution

Meridian disabled public storage access, moved report delivery behind authenticated service access, rotated the pipeline token, and replaced it with environment-scoped short-lived credentials. The build process was changed to use a non-root base image and to fail when a confirmed high-risk dependency was reachable in production code. IaC policies prohibited public storage and unrestricted administrative permissions. Images were signed, and the deployment platform verified signatures and restricted privileged workloads.

The threat model and data-flow diagram were updated. Security ownership was assigned to service teams, while the central security group maintained policy, threat-modeling support, and assurance metrics. A time-limited exception process was introduced for findings requiring additional investigation.

Outcome

The release proceeded after the high-risk paths were addressed, without requiring every warning to be eliminated. Meridian gained stronger evidence about build provenance, deployment permissions, and cloud configuration. Subsequent releases became faster because approved modules, reusable scanning stages, and secure image patterns were available to development teams.

Learning Points

The case illustrates that DevSecOps is not equivalent to indiscriminate automation. Effective practice combines automated detection with threat modeling, reachability analysis, least privilege, ownership, and risk-based decisions. It also demonstrates that cloud, container, pipeline, and dependency risks are interconnected rather than isolated technical categories.

Challenges, Limitations, and Common Mistakes

Security tools have imperfect coverage. SAST may miss business-logic abuse; DAST may fail to authenticate or reach hidden functions; SCA may overstate risks based on vulnerable code that is not used; and IaC scanners may misunderstand dynamic values. Tool output must therefore be triaged by competent professionals.

Common organizational mistakes include treating compliance evidence as proof of security, assigning all responsibility to a central security team, allowing permanent exceptions, using a single shared pipeline credential, and measuring success only by the number of findings closed. Excessive blocking can encourage bypasses, while insufficient enforcement produces recurring weaknesses.

Other risks include alert fatigue, insecure defaults in reusable modules, unreviewed third-party actions, inadequate logging, failure to rotate credentials, and assuming that managed cloud services remove the customer’s obligations. Security controls also have costs: scanning consumes compute resources, strong isolation may reduce convenience, and additional approvals may affect delivery speed. These costs should be made visible and balanced against risk rather than ignored.

Governance, Ethics, and Professional Responsibility

Secure engineering involves sensitive data, powerful credentials, and automated decisions. Testing must be authorized, scoped, and conducted in a manner that minimizes disruption and protects personal information. Security professionals should not access production data merely because technical access is available. Test datasets, masking, retention limits, and audit trails support ethical practice.

Governance should define security requirements, ownership, exception authority, evidence retention, incident escalation, vendor expectations, and regulatory obligations. Privacy principles such as purpose limitation, data minimization, access control, and accountability should influence architecture from the beginning. Software licenses and open-source obligations must also be respected; security teams should not recommend dependency changes without considering compatibility and licensing consequences.

Metrics should support decisions. Useful measures include time to remediate validated critical issues, percentage of production artifacts with provenance evidence, privileged-account coverage by multifactor authentication, IaC policy compliance, dependency freshness, and recurrence of similar defects. Metrics should not become targets that encourage superficial closure or concealment.

Automation, Analytics, and Emerging Practice

Automation enables security checks to operate at delivery speed. Event-driven workflows can open remediation tickets, quarantine exposed secrets, prevent deployment of unsigned images, or trigger credential rotation. Machine learning may assist with anomaly detection, code prioritization, and correlation of cloud events, but it does not replace threat modeling or accountable review. Automated recommendations can inherit incomplete training data, produce biased priorities, or misclassify business context.

Current practice is increasingly concerned with software supply-chain integrity, build provenance, artifact attestations, confidential computing, identity-centric security, and continuous exposure management. Organizations are also combining application inventories, SBOMs, cloud asset data, and runtime telemetry to understand which vulnerable components are actually exposed. The strategic direction is toward verifiable, policy-governed delivery in which security evidence travels with the software artifact.

Best Practices for Secure Delivery

A mature secure engineering program should:

1. Threat-model significant changes and maintain models as architecture evolves.

2. Define security requirements that are specific, testable, and linked to business risk.

3. Use least privilege for developers, services, pipeline jobs, and cloud identities.

4. Protect source repositories, build agents, artifacts, registries, and deployment credentials.

5. Combine SAST, DAST, SCA, secret scanning, IaC analysis, image scanning, and runtime monitoring according to risk.

6. Generate reliable SBOM and provenance information for released artifacts.

7. Treat infrastructure templates, container definitions, and pipeline code as production code.

8. Use approved modules, secure defaults, immutable artifacts, and policy-as-code controls.

9. Make exceptions explicit, owned, time-limited, and subject to review.

10. Use feedback from incidents and production telemetry to improve earlier lifecycle controls.

Chapter Summary

Secure engineering integrates security throughout the design, development, delivery, and operation of digital services. Threat modeling identifies assets, trust boundaries, attack paths, and mitigations; secure design translates those findings into least privilege, defense in depth, secure defaults, and testable requirements. SAST, DAST, SCA, secret scanning, SBOMs, and manual analysis provide different forms of assurance and must be interpreted rather than accepted blindly.

CI/CD pipelines, IaC, containers, and cloud identities are part of the software supply chain and must be protected as high-value assets. Policy as code, artifact provenance, approved modules, image controls, and risk-based gates support repeatable delivery. Cloud security depends on understanding shared responsibility and controlling customer-managed identities, data, configurations, and workloads. Effective DevSecOps combines automation with human judgment, governance, ethical practice, continuous monitoring, and accountable risk decisions.

Key Terms

Threat modeling: A structured process for identifying threats, vulnerabilities, impacts, and mitigations in a system.

Trust boundary: A point where data or authority crosses between components with different security assumptions.

DevSecOps: A collaborative and automated approach integrating security with development and operations.

Shift left: Moving appropriate security activities earlier in the development lifecycle.

Least privilege: Granting an identity or process only the permissions required for its legitimate task.

SAST: Static analysis of source code or intermediate representations without executing the application.

DAST: Security testing performed against a running application.

SCA: Analysis of third-party and transitive software components for vulnerabilities and license information.

SBOM: A structured inventory of software components contained in an artifact.

Secret scanning: Detection of credentials, tokens, keys, and other sensitive values.

CI/CD pipeline: Automated processes that build, test, package, and deploy software.

Infrastructure as code: Declarative or programmable definition of infrastructure resources and configuration.

Policy as code: Machine-enforceable rules used to evaluate configurations or release conditions.

Container image: A packaged filesystem and metadata used to instantiate a container workload.

Software supply chain: The people, tools, code, dependencies, processes, and artifacts involved in producing software.

Shared responsibility model: A cloud-security model dividing obligations between provider and customer.

Security posture management: Continuous assessment of security configurations, exposures, and control effectiveness.

Residual risk: Risk remaining after safeguards and mitigations have been applied.

Review Questions

Short Answer Questions

1. What is the purpose of a trust boundary in a threat model?

2. Distinguish between SAST and DAST.

3. Why should a compromised secret be rotated rather than merely deleted from source code?

4. What is policy as code, and how can it support IaC security?

5. State two customer responsibilities under the cloud shared-responsibility model.

Long Answer Questions

1. Explain a complete threat-modeling process and show how its findings should influence secure design.

2. Discuss the security controls required to protect a CI/CD pipeline from source code to production deployment.

3. Compare SAST, DAST, SCA, secret scanning, and runtime monitoring, including the limitations of each.

4. Explain how containers, image registries, orchestrators, and runtime controls form one security lifecycle.

5. Analyze why DevSecOps requires governance, culture, automation, and risk-based decision-making rather than tools alone.

Analytical / Application Questions

1. An IaC template creates a public storage bucket containing customer reports, while a scanner also reports several low-confidence library warnings. How should a security team prioritize and manage these findings?

2. A deployment pipeline uses one credential with administrative access to development, testing, and production accounts. Design a safer target architecture and explain the transition risks.

3. A cloud application uses managed database and container services, but experiences unauthorized access after an identity-policy change. Analyze the likely shared-responsibility issues and propose preventive and detective controls.

22. Multiple Choice Questions with Answers

Question 1

Which activity most directly converts a threat model into an engineering control?

A. Counting the number of scanner alerts

B. Defining a requirement that only the order service can write to the order database

C. Increasing the number of developers on a project

D. Deleting all historical security reports

Correct Answer: B. Defining a requirement that only the order service can write to the order database

Question 2

A dependency has a serious known vulnerability, but the affected function is not included in the deployed artifact. What is the most appropriate initial conclusion?

A. The vulnerability is automatically critical in production

B. The dependency must always be removed immediately

C. Practical risk may be lower, but the finding requires documented reachability and exposure analysis

D. Software composition analysis is invalid

Correct Answer: C. Practical risk may be lower, but the finding requires documented reachability and exposure analysis

Question 3

Which pipeline design best follows least privilege?

A. One permanent administrator token used by every build job

B. A build job with only the permissions needed to create and publish its artifact

C. A developer’s personal cloud password stored in the repository

D. Production credentials embedded in the container image

Correct Answer: B. A build job with only the permissions needed to create and publish its artifact

Question 4

Why is deleting a secret from the latest source-code commit insufficient?

A. Secrets cannot be used in source code

B. The secret may remain in history, caches, logs, or may already have been copied

C. Deletion makes the repository impossible to build

D. Secret scanners only inspect deployed systems

Correct Answer: B. The secret may remain in history, caches, logs, or may already have been copied

Question 5

Which control is most directly associated with infrastructure-as-code security?

A. Prohibiting all automated deployments

B. Using policy as code to reject public storage and excessive permissions

C. Allowing manual changes without recording them

D. Disabling configuration drift detection

Correct Answer: B. Using policy as code to reject public storage and excessive permissions

Question 6

Which statement best describes the cloud shared-responsibility model?

A. The provider is responsible for every aspect of customer security

B. The customer is responsible for the physical data center in every service model

C. Responsibilities vary by service model, while customers retain obligations for identities, data, and configuration

D. Cloud services eliminate the need for security governance

Correct Answer: C. Responsibilities vary by service model, while customers retain obligations for identities, data, and configuration

Question 7

A container image passes a vulnerability scan but runs as root and has host filesystem access. What does this demonstrate?

A. Image scanning alone provides complete container security

B. Runtime and deployment configuration can create risks beyond package vulnerabilities

C. Root execution is required for all containers

D. Host access improves isolation

Correct Answer: B. Runtime and deployment configuration can create risks beyond package vulnerabilities

Question 8

What is the principal value of an SBOM?

A. It guarantees that every component is vulnerability-free

B. It records the components in an artifact to support vulnerability response and assurance

C. It replaces authorization controls

D. It prevents all supply-chain attacks

Correct Answer: B. It records the components in an artifact to support vulnerability response and assurance

Question 9

Why should security gates be risk-based rather than block every scanner finding?

A. Security findings never matter to production

B. Unselective blocking can create alert fatigue and encourage teams to bypass controls

C. Automated testing is always inaccurate

D. Risk cannot be assessed in software projects

Correct Answer: B. Unselective blocking can create alert fatigue and encourage teams to bypass controls

Question 10

In the Meridian case, why was the pipeline token considered especially dangerous?

A. It could only read public documentation

B. It had authority across production accounts, creating a high-impact supply-chain path

C. It was stored in an approved secret manager

D. It prevented developers from writing application code

Correct Answer: B. It had authority across production accounts, creating a high-impact supply-chain path

Applied Practice Experience in Cybersecurity
Chapter 9 of 15